Merge pull request #362 from nrenner/ringgo-patch
Use boundaries to determine country rules on click
This commit is contained in:
commit
d33413d00e
10 changed files with 220 additions and 104 deletions
|
|
@ -79,7 +79,7 @@ var paths = {
|
|||
'layers/config/geometry.js',
|
||||
],
|
||||
layersConfigDestName: 'layersConf.js',
|
||||
boundaries: 'resources/boundaries/*.geojson',
|
||||
boundaries: ['resources/boundaries/*.geojson', 'resources/boundaries/*.topo.json'],
|
||||
zip: ['dist/**', 'index.html', 'config.template.js', 'keys.template.js'],
|
||||
dest: 'dist',
|
||||
destName: 'brouter-web',
|
||||
|
|
|
|||
38
js/Util.js
38
js/Util.js
|
|
@ -30,6 +30,38 @@ BR.Util = {
|
|||
return new Error(msg);
|
||||
},
|
||||
|
||||
getJson: function (url, context, cb) {
|
||||
BR.Util.get(url, function (err, data) {
|
||||
if (err) {
|
||||
BR.message.showError('Error getting ' + context + ': ' + err);
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
try {
|
||||
var json = JSON.parse(data);
|
||||
cb(null, json);
|
||||
} catch (err) {
|
||||
BR.message.showError('Error parsing ' + context + ': ' + err);
|
||||
console.error(err);
|
||||
cb(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getGeoJson: function (url, context, cb) {
|
||||
BR.Util.getJson(url, context, function (err, data) {
|
||||
if (err) return cb(err);
|
||||
|
||||
var geoJson = data;
|
||||
if (data && data.type && data.type === 'Topology') {
|
||||
var key = Object.keys(data.objects)[0];
|
||||
geoJson = topojson.feature(data, data.objects[key]);
|
||||
}
|
||||
|
||||
cb(null, geoJson);
|
||||
});
|
||||
},
|
||||
|
||||
// check if localStorage is available, especially for catching SecurityError
|
||||
// when cookie settings are blocking access (Chrome, Pale Moon, older Firefox)
|
||||
//
|
||||
|
|
@ -118,6 +150,10 @@ BR.Util = {
|
|||
}
|
||||
|
||||
// fallback when country not available
|
||||
return lang[0] === language;
|
||||
if (language) {
|
||||
return lang[0] === language;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
BR.CircleGoArea = L.Control.extend({
|
||||
circleLayer: null,
|
||||
boundaryLayer: null,
|
||||
outsideAreaRenderer: L.svg({ padding: 1 }),
|
||||
maskRenderer: L.svg({ padding: 2 }),
|
||||
countries: null,
|
||||
countriesMask: null,
|
||||
states: null,
|
||||
statesLoading: false,
|
||||
|
||||
options: {
|
||||
radius: 1000, // in meters
|
||||
stateRules: false,
|
||||
statesUrl: BR.conf.statesUrl || 'dist/boundaries/germany-states.geojson',
|
||||
countriesUrl: BR.conf.countriesUrl || 'dist/boundaries/countries.topo.json',
|
||||
statesUrl: BR.conf.statesUrl || 'dist/boundaries/germany-states.topo.json',
|
||||
overpassBaseUrl: BR.conf.overpassBaseUrl || 'https://overpass-api.de/api/interpreter?data=',
|
||||
shortcut: {
|
||||
draw: {
|
||||
|
|
@ -58,16 +61,22 @@ 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)
|
||||
);
|
||||
this.drawButton.disable();
|
||||
this.once(
|
||||
'countries:loaded',
|
||||
function () {
|
||||
this.drawButton.enable();
|
||||
if (this.marker && !this.marker.dragging.enabled()) {
|
||||
this.marker.dragging.enable();
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
this._loadCountries();
|
||||
|
||||
// preload states in parallel, before clicked country is known, using browser language as indicator
|
||||
if (BR.Util.isCountry('DE')) {
|
||||
this._loadStates();
|
||||
}
|
||||
|
||||
map.on('routing:draw-start', function () {
|
||||
|
|
@ -86,10 +95,12 @@ BR.CircleGoArea = L.Control.extend({
|
|||
this.routing.draw(false);
|
||||
this.pois.draw(false);
|
||||
this.map.on('click', this.onMapClick, this);
|
||||
this.map.addLayer(this.countriesMask);
|
||||
this._unlockOutsideArea();
|
||||
L.DomUtil.addClass(this.map.getContainer(), 'circlego-draw-enabled');
|
||||
} else {
|
||||
this.map.off('click', this.onMapClick, this);
|
||||
this.map.removeLayer(this.countriesMask);
|
||||
this._lockOutsideArea();
|
||||
L.DomUtil.removeClass(this.map.getContainer(), 'circlego-draw-enabled');
|
||||
}
|
||||
|
|
@ -126,17 +137,11 @@ BR.CircleGoArea = L.Control.extend({
|
|||
var url = this.options.overpassBaseUrl + encodeURIComponent(query);
|
||||
|
||||
this.marker.setIcon(this.iconSpinner);
|
||||
BR.Util.get(
|
||||
BR.Util.getJson(
|
||||
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);
|
||||
|
||||
'boundary for coordinate "' + center + '"',
|
||||
L.bind(function (err, osmJson) {
|
||||
if (!err) {
|
||||
if (osmJson.elements.length === 0) {
|
||||
if (adminLevel === 8 || adminLevel === 7) {
|
||||
// admin_level 6 (kreisfreie Stadt)
|
||||
|
|
@ -150,14 +155,9 @@ BR.CircleGoArea = L.Control.extend({
|
|||
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.marker.setIcon(this.icon);
|
||||
}, this)
|
||||
);
|
||||
},
|
||||
|
|
@ -196,21 +196,29 @@ BR.CircleGoArea = L.Control.extend({
|
|||
this.setOutsideArea(ring);
|
||||
},
|
||||
|
||||
_getState: function (center) {
|
||||
var state = null;
|
||||
_getPolygonForPoint: function (center, featureCollection) {
|
||||
var polygon = null;
|
||||
var point = turf.point(center);
|
||||
|
||||
var features = this.states.features;
|
||||
var features = featureCollection.features;
|
||||
for (var i = 0; i < features.length; i++) {
|
||||
var feature = features[i];
|
||||
var inside = turf.booleanPointInPolygon(point, feature);
|
||||
if (inside) {
|
||||
state = feature;
|
||||
polygon = feature;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
return polygon;
|
||||
},
|
||||
|
||||
_getState: function (center) {
|
||||
return this._getPolygonForPoint(center, this.states);
|
||||
},
|
||||
|
||||
_getCountry: function (center) {
|
||||
return this._getPolygonForPoint(center, this.countries);
|
||||
},
|
||||
|
||||
_applyStateRules: function (center) {
|
||||
|
|
@ -244,6 +252,42 @@ BR.CircleGoArea = L.Control.extend({
|
|||
}
|
||||
},
|
||||
|
||||
_applyCountryRules: function (center) {
|
||||
var country = this._getCountry(center);
|
||||
|
||||
if (country) {
|
||||
var name = country.properties.name;
|
||||
|
||||
if (name === 'Germany') {
|
||||
this.options.radius = 15000;
|
||||
|
||||
if (!this.states) {
|
||||
this.marker.setIcon(this.iconSpinner);
|
||||
this.once(
|
||||
'states:loaded',
|
||||
function () {
|
||||
this.marker.setIcon(this.icon);
|
||||
if (this.states) {
|
||||
this._applyStateRules(center);
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
this._loadStates();
|
||||
} else {
|
||||
this._applyStateRules(center);
|
||||
}
|
||||
} else if (name === 'Metropolitan France') {
|
||||
this.options.radius = 20000;
|
||||
this._setNogoCircle(center);
|
||||
} else {
|
||||
console.error('unhandled country: ' + name);
|
||||
}
|
||||
} else {
|
||||
// NOOP, no rules implemented for this location
|
||||
}
|
||||
},
|
||||
|
||||
// debugging
|
||||
_logStates: function (states) {
|
||||
for (var i = 0; i < states.features.length; i++) {
|
||||
|
|
@ -255,19 +299,22 @@ BR.CircleGoArea = L.Control.extend({
|
|||
},
|
||||
|
||||
// debugging
|
||||
_addStatesLayer: function (states) {
|
||||
_addGeoJsonLayer: function (states, options) {
|
||||
// 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,
|
||||
};
|
||||
return L.extend(
|
||||
{
|
||||
weight: 1,
|
||||
color: 'navy',
|
||||
opacity: 0.8,
|
||||
fill: false,
|
||||
interactive: false,
|
||||
},
|
||||
options
|
||||
);
|
||||
},
|
||||
}).addTo(this.map);
|
||||
}, this),
|
||||
|
|
@ -275,29 +322,63 @@ BR.CircleGoArea = L.Control.extend({
|
|||
);
|
||||
},
|
||||
|
||||
_loadStates: function (cb) {
|
||||
BR.Util.get(
|
||||
this.options.statesUrl,
|
||||
L.bind(function (err, data) {
|
||||
if (err) {
|
||||
BR.message.showError('Error getting states: ' + err);
|
||||
return;
|
||||
}
|
||||
_loadStates: function () {
|
||||
if (this.statesLoading) return;
|
||||
|
||||
try {
|
||||
this.states = JSON.parse(data);
|
||||
this.statesLoading = true;
|
||||
BR.Util.getGeoJson(
|
||||
this.options.statesUrl,
|
||||
'states',
|
||||
L.bind(function (err, data) {
|
||||
if (!err) {
|
||||
this.states = 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._addGeoJsonLayer(this.states);
|
||||
}
|
||||
|
||||
this.statesLoading = false;
|
||||
this.fire('states:loaded');
|
||||
}, this)
|
||||
);
|
||||
},
|
||||
|
||||
_loadCountries: function () {
|
||||
BR.Util.getJson(
|
||||
this.options.countriesUrl,
|
||||
'countries',
|
||||
L.bind(function (err, data) {
|
||||
if (err) return;
|
||||
|
||||
var key = Object.keys(data.objects)[0];
|
||||
this.countries = topojson.feature(data, data.objects[key]);
|
||||
|
||||
var union = topojson.merge(data, [data.objects[key]]);
|
||||
this.countriesMask = L.geoJson(union, {
|
||||
renderer: this.maskRenderer,
|
||||
// use Leaflet.snogylop plugin here, turf.mask too slow (~4s) for some reason
|
||||
invert: true,
|
||||
style: function (feature) {
|
||||
return {
|
||||
weight: 1,
|
||||
color: 'darkgreen',
|
||||
opacity: 0.8,
|
||||
fillColor: '#020',
|
||||
fillOpacity: 0.2,
|
||||
className: 'circlego-outside',
|
||||
};
|
||||
},
|
||||
});
|
||||
this.countriesMask.on('click', L.DomEvent.stop);
|
||||
this.countriesMask.bindTooltip(i18next.t('map.not-applicable-here'), {
|
||||
sticky: true,
|
||||
offset: [10, 0],
|
||||
direction: 'right',
|
||||
opacity: 0.8,
|
||||
});
|
||||
|
||||
this.fire('countries:loaded');
|
||||
}, this)
|
||||
);
|
||||
},
|
||||
|
|
@ -325,23 +406,19 @@ BR.CircleGoArea = L.Control.extend({
|
|||
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);
|
||||
}
|
||||
if (!this.countries) {
|
||||
// wait for countries to be loaded (when circlego hash parameter without polylines)
|
||||
this.marker.setIcon(this.iconSpinner);
|
||||
this.once(
|
||||
'countries:loaded',
|
||||
function () {
|
||||
this.marker.setIcon(this.icon);
|
||||
this._applyCountryRules(center);
|
||||
},
|
||||
this
|
||||
);
|
||||
} else {
|
||||
this._setNogoCircle(center);
|
||||
this._applyCountryRules(center);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -368,7 +445,7 @@ BR.CircleGoArea = L.Control.extend({
|
|||
var mask = turf.mask(turf.polygonize(ring));
|
||||
|
||||
this.outsideArea = L.geoJson(mask, {
|
||||
renderer: this.outsideAreaRenderer,
|
||||
renderer: this.maskRenderer,
|
||||
style: function (feature) {
|
||||
return {
|
||||
weight: 4,
|
||||
|
|
@ -427,8 +504,8 @@ BR.CircleGoArea = L.Control.extend({
|
|||
this.clear();
|
||||
marker.addTo(this.circleLayer);
|
||||
|
||||
if (this.options.stateRules && !this.states) {
|
||||
// prevent editing (when called by hash) until states are loaded, see _loadStates call in onAdd
|
||||
// prevent editing (when called by hash) until countries are loaded, see _loadCountries call in onAdd
|
||||
if (!this.countries) {
|
||||
marker.dragging.disable();
|
||||
}
|
||||
|
||||
|
|
@ -538,19 +615,5 @@ BR.CircleGoArea = L.Control.extend({
|
|||
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;
|
||||
return new BR.CircleGoArea(routing, nogos, pois);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -96,8 +96,8 @@
|
|||
"delete-nogo-areas": "Delete all no-go areas",
|
||||
"delete-pois": "Delete all points of interest",
|
||||
"delete-route": "Delete route",
|
||||
"draw-circlego-start": "Draw limited {{radius}}km go-to zone",
|
||||
"draw-circlego-stop": "Stop drawing limited {{radius}}km go-to zone",
|
||||
"draw-circlego-start": "Draw limited go-to zone",
|
||||
"draw-circlego-stop": "Stop drawing limited go-to zone",
|
||||
"draw-poi-start": "Draw points of interest",
|
||||
"draw-poi-stop": "Stop drawing points of interest",
|
||||
"draw-route-start": "Draw route",
|
||||
|
|
@ -132,6 +132,7 @@
|
|||
"edit": "Click to edit",
|
||||
"help": "□ = move / resize, <span class=\"fa fa-trash-o\"></span> = delete,<br>click circle to quit editing"
|
||||
},
|
||||
"not-applicable-here": "not applicable here",
|
||||
"opacity-slider": "Set transparency of route track and markers",
|
||||
"opacity-slider-shortcut": "{{action}}\n(Hold {{key}} key to mute temporarily)",
|
||||
"preview": "Preview",
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@
|
|||
"mapbbcode": "MapBBCode/mapbbcode#v1.2.0",
|
||||
"osmtogeojson": "^3.0.0-beta.4",
|
||||
"promise-polyfill": "^8.2.0",
|
||||
"topojson-client": "^3.1.0",
|
||||
"url-search-params": "~0.5.0",
|
||||
"whatwg-fetch": "^3.5.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,11 +1,25 @@
|
|||
# boundaries
|
||||
|
||||
## germany-states.geojson
|
||||
Downloaded from https://osm-boundaries.com.
|
||||
|
||||
TopoJSON (https://github.com/topojson/topojson) used to convert to topology, simplify and reduce precision:
|
||||
`npm install -g topojson`
|
||||
|
||||
## germany-states
|
||||
|
||||
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"
|
||||
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"
|
||||
|
||||
geo2topo germany-states.geojson | toposimplify -s 3e-12 | topoquantize 1e6 > germany-states.topo.json
|
||||
```
|
||||
|
||||
## countries
|
||||
|
||||
```
|
||||
curl --remote-name --remote-header-name --location --max-redirs -1 "https://osm-boundaries.com/Download/Submit?apiKey=YOUR_API_KEY&db=osm20201109&osmIds=-51477,-1403916"
|
||||
|
||||
geo2topo countries.geojson | toposimplify -s 3e-12 | topoquantize 1e6 > countries.topo.json
|
||||
```
|
||||
|
|
|
|||
1
resources/boundaries/countries.topo.json
Normal file
1
resources/boundaries/countries.topo.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
resources/boundaries/germany-states.topo.json
Normal file
1
resources/boundaries/germany-states.topo.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -8173,7 +8173,7 @@ toidentifier@1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
||||
|
||||
topojson-client@3:
|
||||
topojson-client@3, topojson-client@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/topojson-client/-/topojson-client-3.1.0.tgz#22e8b1ed08a2b922feeb4af6f53b6ef09a467b99"
|
||||
integrity sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue