Determine allowed zone from admin boundaries (#359)
This commit is contained in:
parent
5df0765d51
commit
a5f04dd9cd
14 changed files with 1925 additions and 1171 deletions
|
|
@ -2,3 +2,4 @@ layers/josm/extract.js
|
||||||
layers/collection/extract.js
|
layers/collection/extract.js
|
||||||
gulpfile.js
|
gulpfile.js
|
||||||
dist/brouter-web.js
|
dist/brouter-web.js
|
||||||
|
dist/turf.min.js
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,4 @@ yarn.lock
|
||||||
.tx/
|
.tx/
|
||||||
layers/
|
layers/
|
||||||
locales/*.json
|
locales/*.json
|
||||||
|
resources/boundaries/
|
||||||
|
|
|
||||||
25
gulpfile.js
25
gulpfile.js
|
|
@ -34,7 +34,12 @@ var debug = false;
|
||||||
|
|
||||||
var paths = {
|
var paths = {
|
||||||
// see overrides in package.json
|
// see overrides in package.json
|
||||||
scriptsConfig: mainNpmFiles().filter((f) => RegExp('url-search-params/.*\\.js', 'i').test(f)),
|
scriptsConfig: mainNpmFiles()
|
||||||
|
.filter((f) => RegExp('url-search-params/.*\\.js', 'i').test(f))
|
||||||
|
.concat([
|
||||||
|
// large lib as extra file for faster parallel loading (*.min.js already excluded from bundle)
|
||||||
|
'node_modules/@turf/turf/turf.min.js',
|
||||||
|
]),
|
||||||
scripts: [
|
scripts: [
|
||||||
'node_modules/jquery/dist/jquery.js',
|
'node_modules/jquery/dist/jquery.js',
|
||||||
'node_modules/async/lib/async.js',
|
'node_modules/async/lib/async.js',
|
||||||
|
|
@ -74,6 +79,7 @@ var paths = {
|
||||||
'layers/config/geometry.js',
|
'layers/config/geometry.js',
|
||||||
],
|
],
|
||||||
layersConfigDestName: 'layersConf.js',
|
layersConfigDestName: 'layersConf.js',
|
||||||
|
boundaries: 'resources/boundaries/*.geojson',
|
||||||
zip: ['dist/**', 'index.html', 'config.template.js', 'keys.template.js'],
|
zip: ['dist/**', 'index.html', 'config.template.js', 'keys.template.js'],
|
||||||
dest: 'dist',
|
dest: 'dist',
|
||||||
destName: 'brouter-web',
|
destName: 'brouter-web',
|
||||||
|
|
@ -153,6 +159,10 @@ gulp.task('locales', function () {
|
||||||
return gulp.src(paths.locales).pipe(gulp.dest(paths.dest + '/locales'));
|
return gulp.src(paths.locales).pipe(gulp.dest(paths.dest + '/locales'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task('boundaries', function () {
|
||||||
|
return gulp.src(paths.boundaries).pipe(gulp.dest(paths.dest + '/boundaries'));
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('reload', function (done) {
|
gulp.task('reload', function (done) {
|
||||||
server.reload();
|
server.reload();
|
||||||
done();
|
done();
|
||||||
|
|
@ -303,7 +313,18 @@ gulp.task('layers', function () {
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
'default',
|
'default',
|
||||||
gulp.series('clean', 'scripts_config', 'layers_config', 'layers', 'scripts', 'styles', 'images', 'fonts', 'locales')
|
gulp.series(
|
||||||
|
'clean',
|
||||||
|
'scripts_config',
|
||||||
|
'layers_config',
|
||||||
|
'layers',
|
||||||
|
'scripts',
|
||||||
|
'styles',
|
||||||
|
'images',
|
||||||
|
'fonts',
|
||||||
|
'locales',
|
||||||
|
'boundaries'
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 class="modal-title" data-i18n="credits">Credits</h4>
|
<h4 class="modal-title" data-i18n="credits.title">Credits</h4>
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -168,6 +168,11 @@
|
||||||
Search by
|
Search by
|
||||||
<a href="https://wiki.openstreetmap.org/wiki/Nominatim" target="_blank">Nominatim</a>
|
<a href="https://wiki.openstreetmap.org/wiki/Nominatim" target="_blank">Nominatim</a>
|
||||||
</dd>
|
</dd>
|
||||||
|
<dd data-i18n="[html]credits.boundaries">
|
||||||
|
Administrative Boundaries:
|
||||||
|
<a href="https://osm-boundaries.com" target="_blank">OSM-Boundaries</a>,
|
||||||
|
<a href="https://overpass-api.de/" target="_blank">Overpass API</a>
|
||||||
|
</dd>
|
||||||
<dt data-i18n="credits.elevation-data">Elevation data</dt>
|
<dt data-i18n="credits.elevation-data">Elevation data</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<a href="http://srtm.csi.cgiar.org" target="_blank">CGIAR-CSI SRTM</a>
|
<a href="http://srtm.csi.cgiar.org" target="_blank">CGIAR-CSI SRTM</a>
|
||||||
|
|
@ -1106,6 +1111,7 @@
|
||||||
<script src="keys.js"></script>
|
<script src="keys.js"></script>
|
||||||
<script src="dist/layers.js"></script>
|
<script src="dist/layers.js"></script>
|
||||||
<script src="dist/layersConf.js"></script>
|
<script src="dist/layersConf.js"></script>
|
||||||
|
<script src="dist/turf.min.js"></script>
|
||||||
|
|
||||||
<!-- "gulp inject" for debugging -->
|
<!-- "gulp inject" for debugging -->
|
||||||
<!-- inject:js -->
|
<!-- inject:js -->
|
||||||
|
|
|
||||||
13
js/Util.js
13
js/Util.js
|
|
@ -107,4 +107,17 @@ BR.Util = {
|
||||||
temp.textContent = str;
|
temp.textContent = str;
|
||||||
return temp.innerHTML;
|
return temp.innerHTML;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isCountry: function (country, language) {
|
||||||
|
// de-DE | fr-FR
|
||||||
|
var lang = i18next.languages[0].split('-');
|
||||||
|
|
||||||
|
if (lang.length > 1) {
|
||||||
|
// if available only test country, to avoid e.g. de-CH to match
|
||||||
|
return lang[1] === country;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback when country not available
|
||||||
|
return lang[0] === language;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
21
js/index.js
21
js/index.js
|
|
@ -321,26 +321,14 @@
|
||||||
|
|
||||||
nogos.addTo(map);
|
nogos.addTo(map);
|
||||||
|
|
||||||
var circlegoRadius = null;
|
circlego = BR.circleGoArea(routing, nogos, pois);
|
||||||
var lang = i18next.languages.length && i18next.languages[0];
|
if (circlego != null) {
|
||||||
|
|
||||||
if (lang.startsWith('fr')) {
|
|
||||||
circlegoRadius = 20000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lang.startsWith('de')) {
|
|
||||||
circlegoRadius = 15000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (circlegoRadius != null) {
|
|
||||||
circlego = new BR.CircleGoArea(routing, nogos, pois);
|
|
||||||
circlego.options.radius = circlegoRadius;
|
|
||||||
pois.circlego = circlego;
|
pois.circlego = circlego;
|
||||||
circlego.addTo(map);
|
circlego.addTo(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
var buttons = [drawButton, reverseRouteButton, nogos.getButton()];
|
var buttons = [drawButton, reverseRouteButton, nogos.getButton()];
|
||||||
if (circlegoRadius) buttons.push(circlego.getButton());
|
if (circlego) buttons.push(circlego.getButton());
|
||||||
buttons.push(deletePointButton, deleteRouteButton);
|
buttons.push(deletePointButton, deleteRouteButton);
|
||||||
|
|
||||||
L.easyBar(buttons).addTo(map);
|
L.easyBar(buttons).addTo(map);
|
||||||
|
|
@ -411,8 +399,7 @@
|
||||||
pois.setMarkers(opts.pois);
|
pois.setMarkers(opts.pois);
|
||||||
}
|
}
|
||||||
if (circlego && opts.circlego) {
|
if (circlego && opts.circlego) {
|
||||||
circlego.options.radius = opts.circlego[2];
|
circlego.setOptions(opts);
|
||||||
circlego.setCircle([opts.circlego[0], opts.circlego[1]], opts.polylines != null);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
BR.CircleGoArea = L.Control.extend({
|
BR.CircleGoArea = L.Control.extend({
|
||||||
circleLayer: null,
|
circleLayer: null,
|
||||||
|
boundaryLayer: null,
|
||||||
outsideAreaRenderer: L.svg({ padding: 1 }),
|
outsideAreaRenderer: L.svg({ padding: 1 }),
|
||||||
|
states: null,
|
||||||
|
|
||||||
options: {
|
options: {
|
||||||
radius: 1000, // in meters
|
radius: 1000, // in meters
|
||||||
|
stateRules: false,
|
||||||
|
statesUrl: BR.conf.statesUrl || 'dist/boundaries/germany-states.geojson',
|
||||||
|
overpassBaseUrl: BR.conf.overpassBaseUrl || 'https://overpass-api.de/api/interpreter?data=',
|
||||||
shortcut: {
|
shortcut: {
|
||||||
draw: {
|
draw: {
|
||||||
enable: 73, // char code for 'i'
|
enable: 73, // char code for 'i'
|
||||||
|
|
@ -11,10 +16,12 @@ BR.CircleGoArea = L.Control.extend({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
initialize: function (routing, nogos, pois) {
|
|
||||||
|
initialize: function (routing, nogos, pois, options) {
|
||||||
this.routing = routing;
|
this.routing = routing;
|
||||||
this.nogos = nogos;
|
this.nogos = nogos;
|
||||||
this.pois = pois;
|
this.pois = pois;
|
||||||
|
L.setOptions(this, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
onAdd: function (map) {
|
onAdd: function (map) {
|
||||||
|
|
@ -51,6 +58,18 @@ BR.CircleGoArea = L.Control.extend({
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.options.stateRules) {
|
||||||
|
this.drawButton.disable();
|
||||||
|
this._loadStates(
|
||||||
|
L.bind(function () {
|
||||||
|
this.drawButton.enable();
|
||||||
|
if (this.marker && !this.marker.dragging.enabled()) {
|
||||||
|
this.marker.dragging.enable();
|
||||||
|
}
|
||||||
|
}, this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
map.on('routing:draw-start', function () {
|
map.on('routing:draw-start', function () {
|
||||||
self.draw(false);
|
self.draw(false);
|
||||||
});
|
});
|
||||||
|
|
@ -67,9 +86,11 @@ BR.CircleGoArea = L.Control.extend({
|
||||||
this.routing.draw(false);
|
this.routing.draw(false);
|
||||||
this.pois.draw(false);
|
this.pois.draw(false);
|
||||||
this.map.on('click', this.onMapClick, this);
|
this.map.on('click', this.onMapClick, this);
|
||||||
|
this._unlockOutsideArea();
|
||||||
L.DomUtil.addClass(this.map.getContainer(), 'circlego-draw-enabled');
|
L.DomUtil.addClass(this.map.getContainer(), 'circlego-draw-enabled');
|
||||||
} else {
|
} else {
|
||||||
this.map.off('click', this.onMapClick, this);
|
this.map.off('click', this.onMapClick, this);
|
||||||
|
this._lockOutsideArea();
|
||||||
L.DomUtil.removeClass(this.map.getContainer(), 'circlego-draw-enabled');
|
L.DomUtil.removeClass(this.map.getContainer(), 'circlego-draw-enabled');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -85,91 +106,360 @@ BR.CircleGoArea = L.Control.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setNogoCircle: function (center) {
|
_getBoundary: function (center, adminLevel, adminLevelFallback) {
|
||||||
if (center) {
|
adminLevel = adminLevel || 8;
|
||||||
var polygon = this.circleToPolygon(center, this.options.radius);
|
var query =
|
||||||
var geoJson = JSON.stringify(polygon);
|
'[out:json]; is_in(' +
|
||||||
$('#nogoJSON').val(geoJson);
|
center[1] +
|
||||||
$('#nogoBuffer').val(0);
|
', ' +
|
||||||
this.nogos.uploadNogos();
|
center[0] +
|
||||||
|
')->.a;' +
|
||||||
|
'(area.a[admin_level="' +
|
||||||
|
adminLevel +
|
||||||
|
'"];)->.p; relation(pivot.p); out geom;';
|
||||||
|
|
||||||
var polygonClone = JSON.parse(geoJson);
|
if (adminLevelFallback) {
|
||||||
this.setOutsideArea(polygonClone);
|
// order is important (separate out), so fallback entry always comes last in result
|
||||||
|
query += '(area.a[admin_level="' + adminLevelFallback + '"];)->.p; relation(pivot.p); out geom;';
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = this.options.overpassBaseUrl + encodeURIComponent(query);
|
||||||
|
|
||||||
|
this.marker.setIcon(this.iconSpinner);
|
||||||
|
BR.Util.get(
|
||||||
|
url,
|
||||||
|
L.bind(function (err, data) {
|
||||||
|
if (err) {
|
||||||
|
BR.message.showError('Error getting boundary for coordinate "' + center + '": ' + err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var osmJson = JSON.parse(data);
|
||||||
|
|
||||||
|
if (osmJson.elements.length === 0) {
|
||||||
|
if (adminLevel === 8 || adminLevel === 7) {
|
||||||
|
// admin_level 6 (kreisfreie Stadt)
|
||||||
|
this._getBoundary(center, 6);
|
||||||
|
} else {
|
||||||
|
BR.message.showError('No boundary found for coordinate "' + center + '"');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var geoJson = osmtogeojson(osmJson);
|
||||||
|
|
||||||
|
this._setBoundary(geoJson);
|
||||||
|
|
||||||
|
this.marker.setIcon(this.icon);
|
||||||
|
} catch (err) {
|
||||||
|
BR.message.showError('Error parsing boundary: ' + err);
|
||||||
|
console.error(err);
|
||||||
|
} finally {
|
||||||
|
this.marker.setIcon(this.icon);
|
||||||
|
}
|
||||||
|
}, this)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_setBoundary: function (geoJson) {
|
||||||
|
// drop admin_centre nodes
|
||||||
|
geoJson.features = geoJson.features.filter(function (feature) {
|
||||||
|
return feature.geometry.type !== 'Point';
|
||||||
|
});
|
||||||
|
|
||||||
|
var boundaryLine = turf.polygonToLine(geoJson.features[0]);
|
||||||
|
this.boundaryLayer = L.geoJson(boundaryLine, {
|
||||||
|
style: function (feature) {
|
||||||
|
return {
|
||||||
|
weight: 1,
|
||||||
|
color: 'black',
|
||||||
|
opacity: 0.6,
|
||||||
|
interactive: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}).addTo(this.map);
|
||||||
|
|
||||||
|
var buffer = turf.buffer(geoJson, this.options.radius, { units: 'meters' });
|
||||||
|
|
||||||
|
var ring = turf.polygonToLine(buffer.features[0]);
|
||||||
|
if (ring.type !== 'FeatureCollection') {
|
||||||
|
ring = turf.featureCollection([ring]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hack: it seems there is a bug when using a single closed ring line,
|
||||||
|
cf. https://github.com/nrenner/brouter-web/issues/349#issue-755514458
|
||||||
|
so instead cut into smaller chunks of about half the circumference that worked for circles */
|
||||||
|
var split = turf.lineChunk(ring, 62, { units: 'kilometers' });
|
||||||
|
|
||||||
|
this._setNogo(split);
|
||||||
|
this.setOutsideArea(ring);
|
||||||
|
},
|
||||||
|
|
||||||
|
_getState: function (center) {
|
||||||
|
var state = null;
|
||||||
|
var point = turf.point(center);
|
||||||
|
|
||||||
|
var features = this.states.features;
|
||||||
|
for (var i = 0; i < features.length; i++) {
|
||||||
|
var feature = features[i];
|
||||||
|
var inside = turf.booleanPointInPolygon(point, feature);
|
||||||
|
if (inside) {
|
||||||
|
state = feature;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
|
||||||
|
_applyStateRules: function (center) {
|
||||||
|
var state = this._getState(center);
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
var ref = state.properties.all_tags.ref;
|
||||||
|
|
||||||
|
if (['MV', 'NDS', 'SL', 'SN'].indexOf(ref) !== -1) {
|
||||||
|
// Address
|
||||||
|
this._setNogoCircle(center);
|
||||||
|
} else if (ref === 'ST') {
|
||||||
|
// admin_level 7 + 8 (Gemeinde oder Verbandsgemeinde)
|
||||||
|
// Also select AL 8 (Gemeinde) as fallback where there is no AL 7.
|
||||||
|
// AL 7 first, so we can always take first entry.
|
||||||
|
this._getBoundary(center, 7, 8);
|
||||||
|
} else if (ref === 'BB') {
|
||||||
|
// admin_level 6 (Landkreis oder kreisfreie Stadt)
|
||||||
|
this._getBoundary(center, 6);
|
||||||
|
} else if (ref === 'HH' || state.properties.id === -62422) {
|
||||||
|
// admin_level 4 - (Bundesländer und Stadtstaaten)
|
||||||
|
// Hamburg and Berlin (AL 4) included in states file (Berlin ref missing)
|
||||||
|
this._setBoundary(turf.featureCollection([state]));
|
||||||
|
} else {
|
||||||
|
console.error('unhandled state: ' + ref + ', id: ' + state.properties.id);
|
||||||
|
this._getBoundary(center);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.nogos.clear();
|
// admin_level 8 (Gemeinde)
|
||||||
this.map.removeLayer(this.outsideArea);
|
this._getBoundary(center);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setOutsideArea: function (polygon) {
|
// debugging
|
||||||
var inner = polygon.features[0].geometry.coordinates.concat(polygon.features[1].geometry.coordinates);
|
_logStates: function (states) {
|
||||||
var world = [
|
for (var i = 0; i < states.features.length; i++) {
|
||||||
[180, 90],
|
var state = states.features[i];
|
||||||
[-180, 90],
|
console.log(
|
||||||
[-180, -90],
|
state.properties.all_tags.ref + ', ' + state.properties.all_tags.name + ', ' + state.properties.id
|
||||||
[180, -90],
|
);
|
||||||
[180, 90],
|
|
||||||
];
|
|
||||||
polygon.features[0].geometry.coordinates = [world, inner];
|
|
||||||
polygon.features[0].geometry.type = 'Polygon';
|
|
||||||
polygon.features.pop();
|
|
||||||
|
|
||||||
if (this.outsideArea) {
|
|
||||||
this.map.removeLayer(this.outsideArea);
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
this.outsideArea = L.geoJson(polygon, {
|
// debugging
|
||||||
|
_addStatesLayer: function (states) {
|
||||||
|
// delay, otherwise triggers premature hash update through mapmove
|
||||||
|
setTimeout(
|
||||||
|
L.bind(function () {
|
||||||
|
L.geoJson(states, {
|
||||||
|
style: function (feature) {
|
||||||
|
return {
|
||||||
|
weight: 1,
|
||||||
|
color: 'navy',
|
||||||
|
opacity: 0.8,
|
||||||
|
fill: false,
|
||||||
|
interactive: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}).addTo(this.map);
|
||||||
|
}, this),
|
||||||
|
100
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_loadStates: function (cb) {
|
||||||
|
BR.Util.get(
|
||||||
|
this.options.statesUrl,
|
||||||
|
L.bind(function (err, data) {
|
||||||
|
if (err) {
|
||||||
|
BR.message.showError('Error getting states: ' + err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.states = JSON.parse(data);
|
||||||
|
|
||||||
|
// debugging
|
||||||
|
//this._logStates(this.states);
|
||||||
|
//this._addStatesLayer(this.states);
|
||||||
|
|
||||||
|
this.fire('states:loaded');
|
||||||
|
|
||||||
|
cb();
|
||||||
|
} catch (err) {
|
||||||
|
BR.message.showError('Error parsing states: ' + err);
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}, this)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_setNogo: function (ring) {
|
||||||
|
this.nogoPolylines = L.geoJson(ring, BR.NogoAreas.prototype.polylineOptions);
|
||||||
|
this.nogos.addNogos(null, this.nogoPolylines.getLayers(), null);
|
||||||
|
},
|
||||||
|
|
||||||
|
_removeNogo: function () {
|
||||||
|
if (this.nogoPolylines) {
|
||||||
|
this.nogos.removeNogos(null, this.nogoPolylines.getLayers(), null);
|
||||||
|
this.nogoPolylines = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_setNogoCircle: function (center) {
|
||||||
|
var polygon = this.circleToPolygon(center, this.options.radius);
|
||||||
|
this._setNogo(polygon);
|
||||||
|
this.setOutsideArea(polygon);
|
||||||
|
},
|
||||||
|
|
||||||
|
setNogoRing: function (center) {
|
||||||
|
this._clearLayers();
|
||||||
|
this._removeNogo();
|
||||||
|
|
||||||
|
if (center) {
|
||||||
|
if (this.options.stateRules) {
|
||||||
|
if (!this.states) {
|
||||||
|
// wait for states to be loaded (when circlego hash parameter without polylines)
|
||||||
|
this.marker.setIcon(this.iconSpinner);
|
||||||
|
this.once(
|
||||||
|
'states:loaded',
|
||||||
|
function () {
|
||||||
|
this._applyStateRules(center);
|
||||||
|
this.marker.setIcon(this.icon);
|
||||||
|
},
|
||||||
|
this
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this._applyStateRules(center);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._setNogoCircle(center);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_lockOutsideArea: function () {
|
||||||
|
if (this.outsideArea) {
|
||||||
|
this.outsideArea.eachLayer(function (layer) {
|
||||||
|
layer._path.classList.add('circlego-outside');
|
||||||
|
});
|
||||||
|
this.outsideArea.on('click', L.DomEvent.stop);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_unlockOutsideArea: function () {
|
||||||
|
if (this.outsideArea) {
|
||||||
|
this.outsideArea.eachLayer(function (layer) {
|
||||||
|
layer._path.classList.remove('circlego-outside');
|
||||||
|
});
|
||||||
|
this.outsideArea.off('click', L.DomEvent.stop);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setOutsideArea: function (ring) {
|
||||||
|
var mask = turf.mask(turf.polygonize(ring));
|
||||||
|
|
||||||
|
this.outsideArea = L.geoJson(mask, {
|
||||||
renderer: this.outsideAreaRenderer,
|
renderer: this.outsideAreaRenderer,
|
||||||
style: function (feature) {
|
style: function (feature) {
|
||||||
return {
|
return {
|
||||||
weight: 2,
|
weight: 4,
|
||||||
color: 'black',
|
color: 'black',
|
||||||
opacity: 0.4,
|
opacity: 0.4,
|
||||||
fillColor: 'black',
|
fillColor: 'black',
|
||||||
fillOpacity: 0.4,
|
fillOpacity: 0.4,
|
||||||
className: 'circlego-outside',
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
})
|
smoothFactor: 0.5,
|
||||||
.on('click', L.DomEvent.stop)
|
}).addTo(this.map);
|
||||||
.addTo(this.map);
|
|
||||||
|
this._lockOutsideArea();
|
||||||
},
|
},
|
||||||
|
|
||||||
onMapClick: function (e) {
|
onMapClick: function (e) {
|
||||||
this.setCircle([e.latlng.lng, e.latlng.lat], false);
|
this.setCircle([e.latlng.lng, e.latlng.lat]);
|
||||||
},
|
},
|
||||||
|
|
||||||
setCircle: function (center, skipNogo) {
|
setOptions: function (opts) {
|
||||||
|
this.options.radius = opts.circlego[2];
|
||||||
|
if (opts.polylines) {
|
||||||
|
this.nogoPolylines = L.featureGroup(opts.polylines, BR.NogoAreas.prototype.polylineOptions);
|
||||||
|
}
|
||||||
|
this.setCircle([opts.circlego[0], opts.circlego[1]], opts.polylines);
|
||||||
|
},
|
||||||
|
|
||||||
|
setCircle: function (center, polylines) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var icon = L.VectorMarkers.icon({
|
var icon = (this.icon = L.VectorMarkers.icon({
|
||||||
icon: 'home',
|
icon: 'home',
|
||||||
markerColor: BR.conf.markerColors.circlego,
|
markerColor: BR.conf.markerColors.circlego,
|
||||||
|
}));
|
||||||
|
this.iconSpinner = L.VectorMarkers.icon({
|
||||||
|
icon: 'spinner',
|
||||||
|
spin: true,
|
||||||
|
markerColor: BR.conf.markerColors.circlego,
|
||||||
});
|
});
|
||||||
var marker = L.marker([center[1], center[0]], { icon: icon, draggable: true, name: name })
|
var marker = (this.marker = L.marker([center[1], center[0]], {
|
||||||
|
icon: icon,
|
||||||
|
draggable: true,
|
||||||
|
// prevent being on top of route markers
|
||||||
|
zIndexOffset: -500,
|
||||||
|
})
|
||||||
.on('dragend', function (e) {
|
.on('dragend', function (e) {
|
||||||
self.setNogoCircle([e.target.getLatLng().lng, e.target.getLatLng().lat]);
|
self.setNogoRing([e.target.getLatLng().lng, e.target.getLatLng().lat]);
|
||||||
})
|
})
|
||||||
.on('click', function () {
|
.on('click', function () {
|
||||||
var drawing = self.drawButton.state() == 'deactivate-circlego';
|
var drawing = self.drawButton.state() == 'deactivate-circlego';
|
||||||
if (drawing) {
|
if (drawing) {
|
||||||
self.circleLayer.removeLayer(marker);
|
self.circleLayer.removeLayer(marker);
|
||||||
self.setNogoCircle(undefined);
|
self.setNogoRing(undefined);
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.clear();
|
this.clear();
|
||||||
marker.addTo(this.circleLayer);
|
marker.addTo(this.circleLayer);
|
||||||
if (!skipNogo) {
|
|
||||||
this.setNogoCircle(center);
|
if (this.options.stateRules && !this.states) {
|
||||||
|
// prevent editing (when called by hash) until states are loaded, see _loadStates call in onAdd
|
||||||
|
marker.dragging.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!polylines) {
|
||||||
|
this.setNogoRing(center);
|
||||||
} else {
|
} else {
|
||||||
var polygon = this.circleToPolygon(center, this.options.radius);
|
var features = [];
|
||||||
this.setOutsideArea(polygon);
|
for (var i = 0; i < polylines.length; i++) {
|
||||||
|
features.push(polylines[i].toGeoJSON());
|
||||||
|
}
|
||||||
|
|
||||||
|
var ring = turf.featureCollection(features);
|
||||||
|
this.setOutsideArea(ring);
|
||||||
}
|
}
|
||||||
this.draw(false);
|
this.draw(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_clearLayers: function () {
|
||||||
|
if (this.outsideArea) {
|
||||||
|
this.map.removeLayer(this.outsideArea);
|
||||||
|
this.outsideArea = null;
|
||||||
|
}
|
||||||
|
if (this.boundaryLayer) {
|
||||||
|
this.map.removeLayer(this.boundaryLayer);
|
||||||
|
this.boundaryLayer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
clear: function () {
|
clear: function () {
|
||||||
this.circleLayer.clearLayers();
|
this.circleLayer.clearLayers();
|
||||||
|
this._clearLayers();
|
||||||
},
|
},
|
||||||
|
|
||||||
getButton: function () {
|
getButton: function () {
|
||||||
|
|
@ -210,7 +500,7 @@ BR.CircleGoArea = L.Control.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
circleToPolygon: function (center, radius, numberOfSegments) {
|
circleToPolygon: function (center, radius, numberOfSegments) {
|
||||||
var n = numberOfSegments ? numberOfSegments : 32;
|
var n = numberOfSegments ? numberOfSegments : 64;
|
||||||
|
|
||||||
var inner = [];
|
var inner = [];
|
||||||
for (var i = 0; i < n; ++i) {
|
for (var i = 0; i < n; ++i) {
|
||||||
|
|
@ -229,7 +519,7 @@ BR.CircleGoArea = L.Control.extend({
|
||||||
properties: {},
|
properties: {},
|
||||||
geometry: {
|
geometry: {
|
||||||
type: 'LineString',
|
type: 'LineString',
|
||||||
coordinates: inner.slice(n / 2 - 1),
|
coordinates: inner.slice(n / 2),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -246,3 +536,21 @@ BR.CircleGoArea = L.Control.extend({
|
||||||
});
|
});
|
||||||
|
|
||||||
BR.CircleGoArea.include(L.Evented.prototype);
|
BR.CircleGoArea.include(L.Evented.prototype);
|
||||||
|
|
||||||
|
BR.circleGoArea = function (routing, nogos, pois) {
|
||||||
|
var circleGo = null;
|
||||||
|
var options = {};
|
||||||
|
|
||||||
|
if (BR.Util.isCountry('FR', 'fr')) {
|
||||||
|
options.radius = 20000;
|
||||||
|
} else if (BR.Util.isCountry('DE', 'de')) {
|
||||||
|
options.radius = 15000;
|
||||||
|
options.stateRules = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
circleGo = new BR.CircleGoArea(routing, nogos, pois, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return circleGo;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,10 @@ BR.NogoAreas = L.Control.extend({
|
||||||
fillOpacity: 0.1,
|
fillOpacity: 0.1,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
polylineOptions: {
|
||||||
|
smoothFactor: 0.5,
|
||||||
|
},
|
||||||
|
|
||||||
initialize: function () {
|
initialize: function () {
|
||||||
this._wasRouteDrawing = false;
|
this._wasRouteDrawing = false;
|
||||||
},
|
},
|
||||||
|
|
@ -233,6 +237,9 @@ BR.NogoAreas = L.Control.extend({
|
||||||
var geoJSON = L.geoJson(turf.featureCollection(cleanedGeoJSONFeatures), {
|
var geoJSON = L.geoJson(turf.featureCollection(cleanedGeoJSONFeatures), {
|
||||||
onEachFeature: function (feature, layer) {
|
onEachFeature: function (feature, layer) {
|
||||||
layer.options.nogoWeight = feature.properties.nogoWeight || nogoWeight;
|
layer.options.nogoWeight = feature.properties.nogoWeight || nogoWeight;
|
||||||
|
if (feature.geometry.type === 'LineString') {
|
||||||
|
L.setOptions(layer, self.polylineOptions);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
var nogosPoints = geoJSON.getLayers().filter(function (e) {
|
var nogosPoints = geoJSON.getLayers().filter(function (e) {
|
||||||
|
|
@ -308,6 +315,15 @@ BR.NogoAreas = L.Control.extend({
|
||||||
var polylines = options.polylines;
|
var polylines = options.polylines;
|
||||||
var polygons = options.polygons;
|
var polygons = options.polygons;
|
||||||
this._clear();
|
this._clear();
|
||||||
|
this._addNogos(nogos, polylines, polygons);
|
||||||
|
},
|
||||||
|
|
||||||
|
addNogos: function (nogos, polylines, polygons) {
|
||||||
|
this._addNogos(nogos, polylines, polygons);
|
||||||
|
this._fireUpdate();
|
||||||
|
},
|
||||||
|
|
||||||
|
_addNogos: function (nogos, polylines, polygons) {
|
||||||
if (nogos) {
|
if (nogos) {
|
||||||
for (var i = 0; i < nogos.length; i++) {
|
for (var i = 0; i < nogos.length; i++) {
|
||||||
nogos[i].setStyle(this.style);
|
nogos[i].setStyle(this.style);
|
||||||
|
|
@ -328,6 +344,26 @@ BR.NogoAreas = L.Control.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
removeNogos: function (nogos, polylines, polygons) {
|
||||||
|
if (nogos) {
|
||||||
|
for (var i = 0; i < nogos.length; i++) {
|
||||||
|
this.drawnItems.removeLayer(nogos[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (polylines) {
|
||||||
|
for (var i = 0; i < polylines.length; i++) {
|
||||||
|
this.drawnItems.removeLayer(polylines[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (polygons) {
|
||||||
|
for (var i = 0; i < polygons.length; i++) {
|
||||||
|
this.drawnItems.removeLayer(polygons[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._fireUpdate();
|
||||||
|
},
|
||||||
|
|
||||||
_clear: function () {
|
_clear: function () {
|
||||||
this.drawnItems.clearLayers();
|
this.drawnItems.clearLayers();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -102,8 +102,9 @@ L.BRouter = L.Class.extend({
|
||||||
if (params.pois) {
|
if (params.pois) {
|
||||||
opts.pois = this._parseLonLatNames(params.pois);
|
opts.pois = this._parseLonLatNames(params.pois);
|
||||||
}
|
}
|
||||||
if (params.circlego) {
|
if (params.ringgo || params.circlego) {
|
||||||
var circlego = params.circlego.split(',');
|
var paramRinggo = params.ringgo || params.circlego;
|
||||||
|
var circlego = paramRinggo.split(',');
|
||||||
if (circlego.length == 3) {
|
if (circlego.length == 3) {
|
||||||
circlego = [
|
circlego = [
|
||||||
Number.parseFloat(circlego[0]),
|
Number.parseFloat(circlego[0]),
|
||||||
|
|
@ -122,7 +123,7 @@ L.BRouter = L.Class.extend({
|
||||||
if (urlParams.lonlats != null && urlParams.lonlats.length > 0)
|
if (urlParams.lonlats != null && urlParams.lonlats.length > 0)
|
||||||
args.push(L.Util.template('lonlats={lonlats}', urlParams));
|
args.push(L.Util.template('lonlats={lonlats}', urlParams));
|
||||||
if (urlParams.pois != null && urlParams.pois.length > 0) args.push(L.Util.template('pois={pois}', urlParams));
|
if (urlParams.pois != null && urlParams.pois.length > 0) args.push(L.Util.template('pois={pois}', urlParams));
|
||||||
if (urlParams.circlego != null) args.push(L.Util.template('circlego={circlego}', urlParams));
|
if (urlParams.circlego != null) args.push(L.Util.template('ringgo={circlego}', urlParams));
|
||||||
if (urlParams.nogos != null) args.push(L.Util.template('nogos={nogos}', urlParams));
|
if (urlParams.nogos != null) args.push(L.Util.template('nogos={nogos}', urlParams));
|
||||||
if (urlParams.polylines != null) args.push(L.Util.template('polylines={polylines}', urlParams));
|
if (urlParams.polylines != null) args.push(L.Util.template('polylines={polylines}', urlParams));
|
||||||
if (urlParams.polygons != null) args.push(L.Util.template('polygons={polygons}', urlParams));
|
if (urlParams.polygons != null) args.push(L.Util.template('polygons={polygons}', urlParams));
|
||||||
|
|
@ -433,7 +434,8 @@ L.BRouter = L.Class.extend({
|
||||||
if (j < numbers.length) {
|
if (j < numbers.length) {
|
||||||
nogoWeight = Number.parseFloat(numbers[j++]);
|
nogoWeight = Number.parseFloat(numbers[j++]);
|
||||||
}
|
}
|
||||||
nogos.push(L.polyline(latlngs, { nogoWeight: nogoWeight }));
|
var options = L.extend(BR.NogoAreas.prototype.polylineOptions, { nogoWeight: nogoWeight });
|
||||||
|
nogos.push(L.polyline(latlngs, options));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nogos;
|
return nogos;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
"tooltip": "Show more information about BRouter-Web"
|
"tooltip": "Show more information about BRouter-Web"
|
||||||
},
|
},
|
||||||
"credits": {
|
"credits": {
|
||||||
|
"boundaries": "Administrative Boundaries: <a href=\"https://osm-boundaries.com\" target=\"_blank\">OSM-Boundaries</a>, <a href=\"https://overpass-api.de/\" target=\"_blank\">Overpass API</a>",
|
||||||
"brouter": "BRouter",
|
"brouter": "BRouter",
|
||||||
"brouter-license": "<a target=\"_blank\" href=\"https://brouter.de/brouter\">BRouter</a> © Arndt Brenschede",
|
"brouter-license": "<a target=\"_blank\" href=\"https://brouter.de/brouter\">BRouter</a> © Arndt Brenschede",
|
||||||
"elevation-data": "Elevation data",
|
"elevation-data": "Elevation data",
|
||||||
|
|
@ -23,7 +24,8 @@
|
||||||
"map-data": "Map data",
|
"map-data": "Map data",
|
||||||
"map-tiles": "Map tiles",
|
"map-tiles": "Map tiles",
|
||||||
"nominatim": "Search by <a href=\"https://wiki.openstreetmap.org/wiki/Nominatim\" target=\"_blank\" data-i18n=\"credits.nominatim\">Nominatim</a>",
|
"nominatim": "Search by <a href=\"https://wiki.openstreetmap.org/wiki/Nominatim\" target=\"_blank\" data-i18n=\"credits.nominatim\">Nominatim</a>",
|
||||||
"openstreetmap": "© <a target=\"_blank\" href=\"https://www.openstreetmap.org/copyright\" >OpenStreetMap contributors</a> under <a target=\"_blank\" href=\"https://opendatacommons.org/licenses/odbl/\" >ODbL</a>"
|
"openstreetmap": "© <a target=\"_blank\" href=\"https://www.openstreetmap.org/copyright\" >OpenStreetMap contributors</a> under <a target=\"_blank\" href=\"https://opendatacommons.org/licenses/odbl/\" >ODbL</a>",
|
||||||
|
"title": "Credits"
|
||||||
},
|
},
|
||||||
"export": {
|
"export": {
|
||||||
"format": "Format",
|
"format": "Format",
|
||||||
|
|
|
||||||
13
package.json
13
package.json
|
|
@ -38,7 +38,7 @@
|
||||||
"@bagage/leaflet.restoreview": "1.0.1",
|
"@bagage/leaflet.restoreview": "1.0.1",
|
||||||
"@mapbox/polyline": "^0.2.0",
|
"@mapbox/polyline": "^0.2.0",
|
||||||
"@mapbox/togeojson": "^0.16.0",
|
"@mapbox/togeojson": "^0.16.0",
|
||||||
"@turf/turf": "^5.1.6",
|
"@turf/turf": "^6.2.0",
|
||||||
"Leaflet.vector-markers": "nrenner/Leaflet.vector-markers#2ef80c9",
|
"Leaflet.vector-markers": "nrenner/Leaflet.vector-markers#2ef80c9",
|
||||||
"async": "~0.9.2",
|
"async": "~0.9.2",
|
||||||
"bootbox": "~5.3.4",
|
"bootbox": "~5.3.4",
|
||||||
|
|
@ -72,6 +72,7 @@
|
||||||
"leaflet.snogylop": "^0.4.0",
|
"leaflet.snogylop": "^0.4.0",
|
||||||
"leaflet.stravasegments": "2.3.2",
|
"leaflet.stravasegments": "2.3.2",
|
||||||
"mapbbcode": "MapBBCode/mapbbcode#v1.2.0",
|
"mapbbcode": "MapBBCode/mapbbcode#v1.2.0",
|
||||||
|
"osmtogeojson": "^3.0.0-beta.4",
|
||||||
"promise-polyfill": "^8.2.0",
|
"promise-polyfill": "^8.2.0",
|
||||||
"url-search-params": "~0.5.0",
|
"url-search-params": "~0.5.0",
|
||||||
"whatwg-fetch": "^3.5.0"
|
"whatwg-fetch": "^3.5.0"
|
||||||
|
|
@ -254,6 +255,16 @@
|
||||||
"dist/leaflet-vector-markers.js",
|
"dist/leaflet-vector-markers.js",
|
||||||
"dist/leaflet-vector-markers.css"
|
"dist/leaflet-vector-markers.css"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"osmtogeojson": {
|
||||||
|
"main": [
|
||||||
|
"osmtogeojson.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@turf/turf": {
|
||||||
|
"main": [
|
||||||
|
"turf.min.js"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
resources/boundaries/README.md
Normal file
11
resources/boundaries/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# boundaries
|
||||||
|
|
||||||
|
## germany-states.geojson
|
||||||
|
|
||||||
|
Currently only containing states that do not use the municipality boundary for the Corona 15 km allowed zone rule.
|
||||||
|
|
||||||
|
Downloaded from https://osm-boundaries.com with:
|
||||||
|
|
||||||
|
```
|
||||||
|
curl --remote-name --remote-header-name --location --max-redirs -1 "https://osm-boundaries.com/Download/Submit?apiKey=YOUR_API_KEY&db=osm20201109&osmIds=-28322,-62771,-62372,-62467,-62607,-62422,-62782,-62504&includeAllTags&simplify=0.0001"
|
||||||
|
```
|
||||||
1
resources/boundaries/germany-states.geojson
Normal file
1
resources/boundaries/germany-states.geojson
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue