Exclusion
This commit is contained in:
Generated
+2032
-3
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,7 @@
|
|||||||
"vite": "^4.4.5"
|
"vite": "^4.4.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@turf/turf": "^6.5.0",
|
||||||
"@types/leaflet": "^1.9.3",
|
"@types/leaflet": "^1.9.3",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"leaflet-easybutton": "^2.4.0"
|
"leaflet-easybutton": "^2.4.0"
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import * as L from 'leaflet';
|
||||||
|
import { BannedArea, BannedAreas, NWRElement, tagsToHTML } from './overpass.ts';
|
||||||
|
|
||||||
|
export class ExclusionCircle extends L.Circle {
|
||||||
|
readonly id: string;
|
||||||
|
readonly element: NWRElement;
|
||||||
|
readonly reason: BannedArea;
|
||||||
|
|
||||||
|
constructor(e: NWRElement, options?: L.CircleOptions) {
|
||||||
|
const latLng: [number, number] =
|
||||||
|
e.type === 'node'
|
||||||
|
? [e.lat, e.lon]
|
||||||
|
: [
|
||||||
|
(e.bounds.maxlat + e.bounds.minlat) / 2,
|
||||||
|
(e.bounds.maxlon + e.bounds.minlon) / 2
|
||||||
|
];
|
||||||
|
const reason = getReason(e);
|
||||||
|
super(latLng, {
|
||||||
|
radius: 100,
|
||||||
|
stroke: false,
|
||||||
|
fillOpacity: 0.3,
|
||||||
|
fillColor: colorMap[reason],
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
|
||||||
|
this.id = e.id;
|
||||||
|
this.element = e;
|
||||||
|
this.reason = reason;
|
||||||
|
|
||||||
|
this.bindPopup(
|
||||||
|
`<div class="whitespace-pre-wrap">${tagsToHTML({ reason, ...e.tags })}</div>`,
|
||||||
|
{ maxWidth: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const colorMap: { [reason in BannedArea]: string } = {
|
||||||
|
[BannedAreas.SCHOOL]: 'red',
|
||||||
|
[BannedAreas.UNIVERSITY]: 'orange',
|
||||||
|
[BannedAreas.SPORT]: 'yellow',
|
||||||
|
[BannedAreas.PEDESTRIAN]: 'green',
|
||||||
|
[BannedAreas.OTHER]: 'blue'
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getReason(element: NWRElement): BannedArea {
|
||||||
|
const t = element.tags;
|
||||||
|
if (
|
||||||
|
t.amenity === 'school' ||
|
||||||
|
t.building === 'school' ||
|
||||||
|
t.amenity === 'kindergarten' ||
|
||||||
|
t.leisure === 'playground' ||
|
||||||
|
t.community_centre === 'youth_centre'
|
||||||
|
) {
|
||||||
|
return BannedAreas.SCHOOL;
|
||||||
|
}
|
||||||
|
if (t.building === 'university') {
|
||||||
|
return BannedAreas.UNIVERSITY;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
t.sport !== undefined ||
|
||||||
|
['sports_centre', 'sports_hall', 'stadium', 'track', 'pitch'].includes(
|
||||||
|
t.leisure
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return BannedAreas.SPORT;
|
||||||
|
}
|
||||||
|
if (t.highway !== undefined) {
|
||||||
|
return BannedAreas.PEDESTRIAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BannedAreas.OTHER;
|
||||||
|
}
|
||||||
+56
-60
@@ -1,16 +1,20 @@
|
|||||||
import './style.css';
|
import './style.css';
|
||||||
import 'leaflet/dist/leaflet.css';
|
import 'leaflet/dist/leaflet.css';
|
||||||
import {
|
import {
|
||||||
Circle,
|
|
||||||
Control,
|
Control,
|
||||||
easyButton,
|
easyButton,
|
||||||
FeatureGroup,
|
FeatureGroup,
|
||||||
|
GeoJSON,
|
||||||
Map as LMap,
|
Map as LMap,
|
||||||
|
Polygon,
|
||||||
Popup,
|
Popup,
|
||||||
TileLayer
|
TileLayer
|
||||||
} from 'leaflet';
|
} from 'leaflet';
|
||||||
import 'leaflet-easybutton';
|
import 'leaflet-easybutton';
|
||||||
import { BannedArea, BannedAreas, NWRElement, tagsToHTML } from './overpass.ts';
|
import { BannedArea, BannedAreas, NWRElement } from './overpass.ts';
|
||||||
|
import { colorMap, ExclusionCircle, getReason } from './ExclusionCircle.ts';
|
||||||
|
import { buffer, dissolve, featureCollection } from '@turf/turf';
|
||||||
|
import { Feature } from '@turf/helpers';
|
||||||
|
|
||||||
const m_mono = new TileLayer(
|
const m_mono = new TileLayer(
|
||||||
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||||
@@ -20,7 +24,7 @@ const m_mono = new TileLayer(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const features: { [reason in BannedArea]: FeatureGroup } = {
|
const features: { [reason in BannedArea]: FeatureGroup<ExclusionCircle> } = {
|
||||||
[BannedAreas.SCHOOL]: new FeatureGroup(),
|
[BannedAreas.SCHOOL]: new FeatureGroup(),
|
||||||
[BannedAreas.UNIVERSITY]: new FeatureGroup(),
|
[BannedAreas.UNIVERSITY]: new FeatureGroup(),
|
||||||
[BannedAreas.SPORT]: new FeatureGroup(),
|
[BannedAreas.SPORT]: new FeatureGroup(),
|
||||||
@@ -28,10 +32,10 @@ const features: { [reason in BannedArea]: FeatureGroup } = {
|
|||||||
[BannedAreas.OTHER]: new FeatureGroup()
|
[BannedAreas.OTHER]: new FeatureGroup()
|
||||||
};
|
};
|
||||||
|
|
||||||
const center = [53.0711829, 8.8087718] as [number, number];
|
const center = [53.0552419, 8.7712428] as [number, number];
|
||||||
const map = new LMap('map', {
|
const map = new LMap('map', {
|
||||||
center,
|
center,
|
||||||
zoom: 14,
|
zoom: 19,
|
||||||
zoomControl: true,
|
zoomControl: true,
|
||||||
layers: [m_mono, ...Object.values(features)]
|
layers: [m_mono, ...Object.values(features)]
|
||||||
});
|
});
|
||||||
@@ -39,7 +43,7 @@ const map = new LMap('map', {
|
|||||||
new Popup()
|
new Popup()
|
||||||
.setLatLng(center)
|
.setLatLng(center)
|
||||||
.setContent(
|
.setContent(
|
||||||
`<p>Diese Karte zeigt die Orte, an denen der Konsum von Cannabis verboten ist. Um jeden Ort wird ein Kreis mit Radius von 200 Metern (gemäß <a href="https://dserver.bundestag.de/btd/20/087/2008704.pdf" target="_blank">Gesetzentwurf § 5 Konsumverbot</a>) gezeichnet.</p>
|
`<p>Diese Karte zeigt die Orte, an denen der Konsum von Cannabis verboten ist. Um jeden Ort wird ein Kreis mit Radius von 100 Metern (gemäß <a href="https://dserver.bundestag.de/btd/20/104/2010426.pdf" target="_blank">Beschlussempfehlung § 5 Konsumverbot</a>) gezeichnet.</p>
|
||||||
<p>Oben rechts können die verschiedenen Arten von Orten an- und abgeschaltet werden</p>
|
<p>Oben rechts können die verschiedenen Arten von Orten an- und abgeschaltet werden</p>
|
||||||
<p>Datenquelle: <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMaps</a> via <a href="https://overpass-turbo.eu/" target="_blank">Overpass</a></p>
|
<p>Datenquelle: <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMaps</a> via <a href="https://overpass-turbo.eu/" target="_blank">Overpass</a></p>
|
||||||
<p>Keine Garantie für Korrektheit!</p>`
|
<p>Keine Garantie für Korrektheit!</p>`
|
||||||
@@ -50,6 +54,8 @@ map.on('moveend', async () => {
|
|||||||
await fetchData();
|
await fetchData();
|
||||||
});
|
});
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
const areas = new FeatureGroup();
|
||||||
|
let exclusionZone: GeoJSON | undefined;
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
if (map.getZoom() < 12 || loading) {
|
if (map.getZoom() < 12 || loading) {
|
||||||
return;
|
return;
|
||||||
@@ -85,67 +91,57 @@ out geom;`;
|
|||||||
.catch(() => ({ elements: [] }))
|
.catch(() => ({ elements: [] }))
|
||||||
.then((r) => r.elements as NWRElement[]);
|
.then((r) => r.elements as NWRElement[]);
|
||||||
|
|
||||||
Object.values(features).forEach((f) => f.clearLayers());
|
const ids = elements.map((e) => e.id);
|
||||||
|
const exclude: string[] = [];
|
||||||
elements.forEach((e) => {
|
Object.values(features).forEach((f) =>
|
||||||
const reason = getReason(e);
|
f.eachLayer((l) => {
|
||||||
const marker = new Circle(
|
const id = (l as ExclusionCircle).id;
|
||||||
e.type === 'node' ? [e.lat, e.lon] : [e.bounds.maxlat, e.bounds.maxlon],
|
if (ids.includes(id)) {
|
||||||
200,
|
exclude.push(id);
|
||||||
{
|
return;
|
||||||
radius: 200,
|
|
||||||
stroke: false,
|
|
||||||
fillOpacity: 0.3,
|
|
||||||
fillColor: colorMap[reason]
|
|
||||||
}
|
}
|
||||||
).bindPopup(
|
f.removeLayer(l);
|
||||||
`<div class="whitespace-pre-wrap">${tagsToHTML({ reason, ...e.tags })}</div>`,
|
})
|
||||||
{ maxWidth: 500 }
|
);
|
||||||
);
|
|
||||||
|
|
||||||
features[reason].addLayer(marker);
|
const buffers: Feature[] = [];
|
||||||
|
elements.forEach((e) => {
|
||||||
|
if (exclude.includes(e.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const marker = new ExclusionCircle(e);
|
||||||
|
features[marker.reason].addLayer(marker);
|
||||||
|
|
||||||
|
if (e.type === 'way') {
|
||||||
|
const area = new Polygon(
|
||||||
|
e.geometry.map(({ lat, lon }) => ({ lat, lng: lon })),
|
||||||
|
{
|
||||||
|
fillColor: colorMap[getReason(e)],
|
||||||
|
fillOpacity: 0.4,
|
||||||
|
fill: true,
|
||||||
|
stroke: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
area.addTo(map);
|
||||||
|
const b = buffer(area.toGeoJSON(), 100, { units: 'meters' });
|
||||||
|
|
||||||
|
// new GeoJSON(b).addTo(map);
|
||||||
|
buffers.push(b);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// const forbidden = buffers.reduce((u, b) => union(u, b), buffers.pop());
|
||||||
|
// new GeoJSON(forbidden).addTo(map);
|
||||||
|
const b = dissolve(featureCollection(buffers));
|
||||||
|
exclusionZone?.removeFrom(map);
|
||||||
|
exclusionZone = new GeoJSON(b, {
|
||||||
|
style: { color: 'red', fillOpacity: 0.1, weight: 1 }
|
||||||
|
});
|
||||||
|
exclusionZone.addTo(map);
|
||||||
|
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const colorMap: { [reason in BannedArea]: string } = {
|
|
||||||
[BannedAreas.SCHOOL]: 'red',
|
|
||||||
[BannedAreas.UNIVERSITY]: 'orange',
|
|
||||||
[BannedAreas.SPORT]: 'yellow',
|
|
||||||
[BannedAreas.PEDESTRIAN]: 'green',
|
|
||||||
[BannedAreas.OTHER]: 'blue'
|
|
||||||
};
|
|
||||||
|
|
||||||
function getReason(element: NWRElement): BannedArea {
|
|
||||||
const t = element.tags;
|
|
||||||
if (
|
|
||||||
t.amenity === 'school' ||
|
|
||||||
t.building === 'school' ||
|
|
||||||
t.amenity === 'kindergarten' ||
|
|
||||||
t.leisure === 'playground' ||
|
|
||||||
t.community_centre === 'youth_centre'
|
|
||||||
) {
|
|
||||||
return BannedAreas.SCHOOL;
|
|
||||||
}
|
|
||||||
if (t.building === 'university') {
|
|
||||||
return BannedAreas.UNIVERSITY;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
t.sport !== undefined ||
|
|
||||||
['sports_centre', 'sports_hall', 'stadium', 'track', 'pitch'].includes(
|
|
||||||
t.leisure
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return BannedAreas.SPORT;
|
|
||||||
}
|
|
||||||
if (t.highway !== undefined) {
|
|
||||||
return BannedAreas.PEDESTRIAN;
|
|
||||||
}
|
|
||||||
|
|
||||||
return BannedAreas.OTHER;
|
|
||||||
}
|
|
||||||
|
|
||||||
new Control.Layers(
|
new Control.Layers(
|
||||||
{},
|
{},
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
|
|||||||
Reference in New Issue
Block a user