Do not show out-of-range features on speelplekken layer, fix handling of mutlipolygons in 'inside', better tests

This commit is contained in:
Pieter Vander Vennet 2021-05-14 02:25:30 +02:00
parent 117b0bddb1
commit 6f457a6f0d
26 changed files with 1284 additions and 770 deletions

View file

@ -87,7 +87,8 @@ export default class LayerConfig {
geojsonSource: json.source["geoJson"], geojsonSource: json.source["geoJson"],
geojsonSourceLevel: json.source["geoJsonZoomLevel"], geojsonSourceLevel: json.source["geoJsonZoomLevel"],
overpassScript: json.source["overpassScript"], overpassScript: json.source["overpassScript"],
}); isOsmCache: json.source["isOsmCache"]
}, this.id);
} else { } else {
this.source = new SourceConfig({ this.source = new SourceConfig({
osmTags: legacy osmTags: legacy

View file

@ -30,20 +30,34 @@ export interface LayerConfigJson {
* This determines where the data for the layer is fetched. * This determines where the data for the layer is fetched.
* There are some options: * There are some options:
* *
* source: {osmTags: "key=value"} will fetch all objects with given tags from OSM. Currently, this will create a query to overpass and fetch the data - in the future this might fetch from the OSM API * # Query OSM directly
* source: {geoJson: "https://my.source.net/some-geo-data.geojson"} to fetch a geojson from a third party source * source: {osmTags: "key=value"}
* source: {geoJson: "https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson", geoJsonZoomLevel: 14} to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer * will fetch all objects with given tags from OSM.
* Currently, this will create a query to overpass and fetch the data - in the future this might fetch from the OSM API
* *
* # Query OSM Via the overpass API with a custom script
* source: {overpassScript: "<custom overpass tags>"} when you want to do special things. _This should be really rare_. * source: {overpassScript: "<custom overpass tags>"} when you want to do special things. _This should be really rare_.
* This means that the data will be pulled from overpass with this script, and will ignore the osmTags for the query * This means that the data will be pulled from overpass with this script, and will ignore the osmTags for the query
* However, for the rest of the pipeline, the OsmTags will _still_ be used. This is important to enable layers etc... * However, for the rest of the pipeline, the OsmTags will _still_ be used. This is important to enable layers etc...
* *
* *
* # A single geojson-file
* source: {geoJson: "https://my.source.net/some-geo-data.geojson"}
* fetches a geojson from a third party source
*
* # A tiled geojson source
* source: {geoJson: "https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson", geoJsonZoomLevel: 14}
* to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer
*
*
* Note that both geojson-options might set a flag 'isOsmCache' indicating that the data originally comes from OSM too
*
*
* NOTE: the previous format was 'overpassTags: AndOrTagCOnfigJson | string', which is interpreted as a shorthand for source: {osmTags: "key=value"} * NOTE: the previous format was 'overpassTags: AndOrTagCOnfigJson | string', which is interpreted as a shorthand for source: {osmTags: "key=value"}
* While still supported, this is considered deprecated * While still supported, this is considered deprecated
*/ */
source: { osmTags: AndOrTagConfigJson | string } | source: { osmTags: AndOrTagConfigJson | string } |
{ osmTags: AndOrTagConfigJson | string, geoJson: string, geoJsonZoomLevel?: number } | { osmTags: AndOrTagConfigJson | string, geoJson: string, geoJsonZoomLevel?: number, isOsmCache?: boolean } |
{ osmTags: AndOrTagConfigJson | string, overpassScript: string } { osmTags: AndOrTagConfigJson | string, overpassScript: string }
/** /**

View file

@ -6,13 +6,15 @@ export default class SourceConfig {
overpassScript?: string; overpassScript?: string;
geojsonSource?: string; geojsonSource?: string;
geojsonZoomLevel?: number; geojsonZoomLevel?: number;
isOsmCacheLayer: boolean;
constructor(params: { constructor(params: {
osmTags?: TagsFilter, osmTags?: TagsFilter,
overpassScript?: string, overpassScript?: string,
geojsonSource?: string, geojsonSource?: string,
isOsmCache?: boolean,
geojsonSourceLevel?: number geojsonSourceLevel?: number
}) { }, context?: string) {
let defined = 0; let defined = 0;
if (params.osmTags) { if (params.osmTags) {
@ -27,9 +29,14 @@ export default class SourceConfig {
if (defined == 0) { if (defined == 0) {
throw "Source: nothing correct defined in the source" throw "Source: nothing correct defined in the source"
} }
if(params.isOsmCache && params.geojsonSource == undefined){
console.error(params)
throw `Source said it is a OSM-cached layer, but didn't define the actual source of the cache (in context ${context})`
}
this.osmTags = params.osmTags; this.osmTags = params.osmTags;
this.overpassScript = params.overpassScript; this.overpassScript = params.overpassScript;
this.geojsonSource = params.geojsonSource; this.geojsonSource = params.geojsonSource;
this.geojsonZoomLevel = params.geojsonSourceLevel; this.geojsonZoomLevel = params.geojsonSourceLevel;
this.isOsmCacheLayer = params.isOsmCache ?? false;
} }
} }

View file

@ -2,7 +2,7 @@ import {FixedUiElement} from "./UI/Base/FixedUiElement";
import CheckBox from "./UI/Input/CheckBox"; import CheckBox from "./UI/Input/CheckBox";
import {Basemap} from "./UI/BigComponents/Basemap"; import {Basemap} from "./UI/BigComponents/Basemap";
import State from "./State"; import State from "./State";
import LoadFromOverpass from "./Logic/Actors/UpdateFromOverpass"; import LoadFromOverpass from "./Logic/Actors/OverpassFeatureSource";
import {UIEventSource} from "./Logic/UIEventSource"; import {UIEventSource} from "./Logic/UIEventSource";
import {QueryParameters} from "./Logic/Web/QueryParameters"; import {QueryParameters} from "./Logic/Web/QueryParameters";
import StrayClickHandler from "./Logic/Actors/StrayClickHandler"; import StrayClickHandler from "./Logic/Actors/StrayClickHandler";

View file

@ -10,9 +10,9 @@ import {TagsFilter} from "../Tags/TagsFilter";
import SimpleMetaTagger from "../SimpleMetaTagger"; import SimpleMetaTagger from "../SimpleMetaTagger";
export default class UpdateFromOverpass implements FeatureSource { export default class OverpassFeatureSource implements FeatureSource {
public readonly name = "UpdateFromOverpass" public readonly name = "OverpassFeatureSource"
/** /**
* The last loaded features of the geojson * The last loaded features of the geojson

View file

@ -84,7 +84,6 @@ export class ElementStorage {
} }
} }
if (somethingChanged) { if (somethingChanged) {
console.trace(`Merging multiple instances of ${elementId}: ` + debug_msg.join(", ")+" newProperties: ", newProperties)
es.ping(); es.ping();
} }
return es; return es;

View file

@ -38,7 +38,7 @@ Some advanced functions are available on <b>feat</b> as well:
` `
private static readonly OverlapFunc = new ExtraFunction( private static readonly OverlapFunc = new ExtraFunction(
"overlapWith", "overlapWith",
"Gives a list of features from the specified layer which this feature overlaps with, the amount of overlap in m². The returned value is <b>{ feat: GeoJSONFeature, overlap: number}</b>", "Gives a list of features from the specified layer which this feature (partly) overlaps with. If the current feature is a point, all features that embed the point are given. The returned value is <code>{ feat: GeoJSONFeature, overlap: number}[]</code> where <code>overlap</code> is the overlapping surface are (in m²) for areas, the overlapping length (in meter) if the current feature is a line or <code>undefined</code> if the current feature is a point",
["...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)"], ["...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)"],
(params, feat) => { (params, feat) => {
return (...layerIds: string[]) => { return (...layerIds: string[]) => {

View file

@ -6,16 +6,15 @@ export class GeoOperations {
return turf.area(feature); return turf.area(feature);
} }
static centerpoint(feature: any) static centerpoint(feature: any) {
{ const newFeature = turf.center(feature);
const newFeature= turf.center(feature);
newFeature.properties = feature.properties; newFeature.properties = feature.properties;
newFeature.id = feature.id; newFeature.id = feature.id;
return newFeature; return newFeature;
} }
static centerpointCoordinates(feature: any): [number, number]{ static centerpointCoordinates(feature: any): [number, number] {
// @ts-ignore // @ts-ignore
return turf.center(feature).geometry.coordinates; return turf.center(feature).geometry.coordinates;
} }
@ -25,7 +24,7 @@ export class GeoOperations {
* @param lonlat0 * @param lonlat0
* @param lonlat1 * @param lonlat1
*/ */
static distanceBetween(lonlat0: [number,number], lonlat1:[number, number]){ static distanceBetween(lonlat0: [number, number], lonlat1: [number, number]) {
return turf.distance(lonlat0, lonlat1) return turf.distance(lonlat0, lonlat1)
} }
@ -33,28 +32,92 @@ export class GeoOperations {
* Calculates the overlap of 'feature' with every other specified feature. * Calculates the overlap of 'feature' with every other specified feature.
* The features with which 'feature' overlaps, are returned together with their overlap area in m² * The features with which 'feature' overlaps, are returned together with their overlap area in m²
* *
* If 'feature' is a LineString, the features in which this feature is (partly) embedded is returned, the overlap length in meter is given
*
* If 'feature' is a point, it will return every feature the point is embedded in. Overlap will be undefined * If 'feature' is a point, it will return every feature the point is embedded in. Overlap will be undefined
*/ */
static calculateOverlap(feature: any, static calculateOverlap(feature: any, otherFeatures: any[]): { feat: any, overlap: number }[] {
otherFeatures: any[]): { feat: any, overlap: number }[] {
const featureBBox = BBox.get(feature); const featureBBox = BBox.get(feature);
const result : { feat: any, overlap: number }[] = []; const result: { feat: any, overlap: number }[] = [];
if (feature.geometry.type === "Point") { if (feature.geometry.type === "Point") {
const coor = feature.geometry.coordinates; const coor = feature.geometry.coordinates;
for (const otherFeature of otherFeatures) { for (const otherFeature of otherFeatures) {
if (otherFeature.geometry === undefined) {
console.error("No geometry for feature ", feature)
throw "List of other features contains a feature without geometry an undefined"
}
let otherFeatureBBox = BBox.get(otherFeature); let otherFeatureBBox = BBox.get(otherFeature);
if (!featureBBox.overlapsWith(otherFeatureBBox)) { if (!featureBBox.overlapsWith(otherFeatureBBox)) {
continue; continue;
} }
if (this.inside(coor, otherFeatures)) { if (this.inside(coor, otherFeature)) {
result.push({ feat: otherFeatures, overlap: undefined }) result.push({feat: otherFeature, overlap: undefined})
} }
} }
return result; return result;
} }
if (feature.geometry.type === "LineString") {
for (const otherFeature of otherFeatures) {
const otherFeatureBBox = BBox.get(otherFeature);
const overlaps = featureBBox.overlapsWith(otherFeatureBBox)
if (!overlaps) {
continue;
}
// Calculate the length of the intersection
try {
let intersectionPoints = turf.lineIntersect(feature, otherFeature);
if (intersectionPoints.features.length == 0) {
// No intersections.
// If one point is inside of the polygon, all points are
const coors = feature.geometry.coordinates;
const startCoor = coors[0]
if (this.inside(startCoor, otherFeature)) {
result.push({feat: otherFeature, overlap: this.lengthInMeters(feature)})
}
continue;
}
let intersectionPointsArray = intersectionPoints.features.map(d => {
return d.geometry.coordinates
});
if (intersectionPointsArray.length == 1) {
// We need to add the start- or endpoint of the current feature, depending on which one is embedded
const coors = feature.geometry.coordinates;
const startCoor = coors[0]
if (this.inside(startCoor, otherFeature)) {
// The startpoint is embedded
intersectionPointsArray.push(startCoor)
} else {
intersectionPointsArray.push(coors[coors.length - 1])
}
}
let intersection = turf.lineSlice(turf.point(intersectionPointsArray[0]), turf.point(intersectionPointsArray[1]), feature);
if (intersection == null) {
continue;
}
const intersectionSize = turf.length(intersection); // in km
result.push({feat: otherFeature, overlap: intersectionSize * 1000})
} catch (exception) {
console.warn("EXCEPTION CAUGHT WHILE INTERSECTING: ", exception);
}
}
return result;
}
if (feature.geometry.type === "Polygon" || feature.geometry.type === "MultiPolygon") { if (feature.geometry.type === "Polygon" || feature.geometry.type === "MultiPolygon") {
for (const otherFeature of otherFeatures) { for (const otherFeature of otherFeatures) {
@ -74,7 +137,7 @@ export class GeoOperations {
const intersectionSize = turf.area(intersection); // in m² const intersectionSize = turf.area(intersection); // in m²
result.push({feat: otherFeature, overlap: intersectionSize}) result.push({feat: otherFeature, overlap: intersectionSize})
} catch (exception) { } catch (exception) {
console.log("EXCEPTION CAUGHT WHILE INTERSECTING: ", exception); console.warn("EXCEPTION CAUGHT WHILE INTERSECTING: ", exception);
} }
} }
@ -92,6 +155,32 @@ export class GeoOperations {
return false; return false;
} }
if (feature.geometry.type === "MultiPolygon") {
const coordinates = feature.geometry.coordinates[0];
const outerPolygon = coordinates[0];
const inside = GeoOperations.inside(pointCoordinate, {
geometry: {
type: 'Polygon',
coordinates: [outerPolygon]
}
})
if (!inside) {
return false;
}
for (let i = 1; i < coordinates.length; i++) {
const inHole = GeoOperations.inside(pointCoordinate, {
geometry: {
type: 'Polygon',
coordinates: [coordinates[i]]
}
})
if (inHole) {
return false;
}
}
return true;
}
const x: number = pointCoordinate[0]; const x: number = pointCoordinate[0];
const y: number = pointCoordinate[1]; const y: number = pointCoordinate[1];
@ -125,7 +214,7 @@ export class GeoOperations {
} }
class BBox{ class BBox {
readonly maxLat: number; readonly maxLat: number;
readonly maxLon: number; readonly maxLon: number;
@ -148,29 +237,6 @@ class BBox{
this.check(); this.check();
} }
private check() {
if (isNaN(this.maxLon) || isNaN(this.maxLat) || isNaN(this.minLon) || isNaN(this.minLat)) {
console.log(this);
throw "BBOX has NAN";
}
}
public overlapsWith(other: BBox) {
this.check();
other.check();
if (this.maxLon < other.minLon) {
return false;
}
if (this.maxLat < other.minLat) {
return false;
}
if (this.minLon > other.maxLon) {
return false;
}
return this.minLat <= other.maxLat;
}
static get(feature) { static get(feature) {
if (feature.bbox?.overlapsWith === undefined) { if (feature.bbox?.overlapsWith === undefined) {
@ -195,4 +261,27 @@ class BBox{
return feature.bbox; return feature.bbox;
} }
public overlapsWith(other: BBox) {
this.check();
other.check();
if (this.maxLon < other.minLon) {
return false;
}
if (this.maxLat < other.minLat) {
return false;
}
if (this.minLon > other.maxLon) {
return false;
}
return this.minLat <= other.maxLat;
}
private check() {
if (isNaN(this.maxLon) || isNaN(this.maxLat) || isNaN(this.minLon) || isNaN(this.minLat)) {
console.log(this);
throw "BBOX has NAN";
}
}
} }

View file

@ -26,7 +26,6 @@ export default class MetaTagging {
layers: LayerConfig[], layers: LayerConfig[],
includeDates = true) { includeDates = true) {
console.debug("Adding meta tags to all features")
for (const metatag of SimpleMetaTagger.metatags) { for (const metatag of SimpleMetaTagger.metatags) {
if (metatag.includesDates && !includeDates) { if (metatag.includesDates && !includeDates) {
// We do not add dated entries // We do not add dated entries
@ -95,9 +94,17 @@ export default class MetaTagging {
const f = (featuresPerLayer, feature: any) => { const f = (featuresPerLayer, feature: any) => {
try { try {
feature.properties[key] =func(feature); let result = func(feature);
if(result === undefined || result === ""){
return;
}
if(typeof result !== "string"){
// Make sure it is a string!
result = "" + result;
}
feature.properties[key] = result;
} catch (e) { } catch (e) {
console.error("Could not calculate a metatag defined by " + code + " due to " + e + ". This is code defined in the theme. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features") console.error("Could not calculate a metatag defined by " + code + " due to " + e + ". This is code defined in the theme. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features", e)
} }
} }

View file

@ -49,7 +49,7 @@ export class RegexTag extends TagsFilter {
continue; continue;
} }
if (RegexTag.doesMatch(key, this.key)) { if (RegexTag.doesMatch(key, this.key)) {
const value = tags[key] const value = tags[key] ?? "";
return RegexTag.doesMatch(value, this.value) != this.invert; return RegexTag.doesMatch(value, this.value) != this.invert;
} }
} }

View file

@ -13,7 +13,7 @@ import BaseLayer from "./Models/BaseLayer";
import Loc from "./Models/Loc"; import Loc from "./Models/Loc";
import Constants from "./Models/Constants"; import Constants from "./Models/Constants";
import UpdateFromOverpass from "./Logic/Actors/UpdateFromOverpass"; import OverpassFeatureSource from "./Logic/Actors/OverpassFeatureSource";
import LayerConfig from "./Customizations/JSON/LayerConfig"; import LayerConfig from "./Customizations/JSON/LayerConfig";
import TitleHandler from "./Logic/Actors/TitleHandler"; import TitleHandler from "./Logic/Actors/TitleHandler";
import PendingChangesUploader from "./Logic/Actors/PendingChangesUploader"; import PendingChangesUploader from "./Logic/Actors/PendingChangesUploader";
@ -57,7 +57,7 @@ export default class State {
public favouriteLayers: UIEventSource<string[]>; public favouriteLayers: UIEventSource<string[]>;
public layerUpdater: UpdateFromOverpass; public layerUpdater: OverpassFeatureSource;
public osmApiFeatureSource : OsmApiFeatureSource ; public osmApiFeatureSource : OsmApiFeatureSource ;

View file

@ -40,11 +40,15 @@
"override": { "override": {
"source": { "source": {
"geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{z}_{x}_{y}.geojson", "geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{z}_{x}_{y}.geojson",
"geoJsonZoomLevel": 14 "geoJsonZoomLevel": 14,
"isOsmCache": true
}, },
"icon": "./assets/themes/speelplekken/speelbos.svg", "icon": "./assets/themes/speelplekken/speelbos.svg",
"minzoom": 12 "minzoom": 12
} },
"calculatedTags": [
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes' : 'no'"
]
}, },
{ {
"builtin": "playground", "builtin": "playground",
@ -54,8 +58,12 @@
"source": { "source": {
"geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson", "geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson",
"geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{layer}_{z}_{x}_{y}.geojson", "geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{layer}_{z}_{x}_{y}.geojson",
"geoJsonZoomLevel": 14 "geoJsonZoomLevel": 14,
} "isOsmCache": true
},
"calculatedTags": [
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes' : 'no'"
]
} }
}, },
{ {
@ -66,8 +74,12 @@
"source": { "source": {
"geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson", "geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson",
"geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{layer}_{z}_{x}_{y}.geojson", "geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{layer}_{z}_{x}_{y}.geojson",
"geoJsonZoomLevel": 14 "geoJsonZoomLevel": 14,
} "isOsmCache": true
},
"calculatedTags": [
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes' : 'no'"
]
} }
}, },
{ {
@ -78,8 +90,12 @@
"source": { "source": {
"geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson", "geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson",
"geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{layer}_{z}_{x}_{y}.geojson", "geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{layer}_{z}_{x}_{y}.geojson",
"geoJsonZoomLevel": 14 "geoJsonZoomLevel": 14,
} "isOsmCache": true
},
"calculatedTags": [
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes' : 'no'"
]
} }
}, },
{ {
@ -89,20 +105,26 @@
"source": { "source": {
"geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson", "geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson",
"geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{layer}_{z}_{x}_{y}.geojson", "geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{layer}_{z}_{x}_{y}.geojson",
"geoJsonZoomLevel": 14 "geoJsonZoomLevel": 14,
} "isOsmCache": true
},
"calculatedTags": [
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes' : 'no'"
]
} }
}, },
{ {
"builtin": "slow_roads", "builtin": "slow_roads",
"override": { "override": {
"calculatedTags": [ "calculatedTags": [
"_part_of_walking_routes=feat.memberships().map(r => \"<a href='#relation/\"+r.relation.id+\"'>\" + r.relation.tags.name + \"</a>\").join(', ')" "_part_of_walking_routes=feat.memberships().map(r => \"<a href='#relation/\"+r.relation.id+\"'>\" + r.relation.tags.name + \"</a>\").join(', ')",
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes' : 'no'"
], ],
"source": { "source": {
"geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson", "geoJson": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson",
"geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{layer}_{z}_{x}_{y}.geojson", "geoJsonWeb": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{layer}_{z}_{x}_{y}.geojson",
"geoJsonZoomLevel": 14 "geoJsonZoomLevel": 14,
"isOsmCache": true
} }
} }
}, },
@ -121,7 +143,8 @@
] ]
}, },
"geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{z}_{x}_{y}.geojson", "geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{z}_{x}_{y}.geojson",
"geoJsonZoomLevel": 14 "geoJsonZoomLevel": 14,
"isOsmCache": true
}, },
"title": { "title": {
"render": "Wandeling <i>{name}</i>", "render": "Wandeling <i>{name}</i>",
@ -227,5 +250,16 @@
"render": "Maakt deel uit van {_part_of_walking_routes}", "render": "Maakt deel uit van {_part_of_walking_routes}",
"condition": "_part_of_walking_routes~*" "condition": "_part_of_walking_routes~*"
} }
],
"overrideAll": {
"isShown": {
"render": "yes",
"mappings": [
{
"if": "_is_shadowed=yes",
"then": "no"
}
] ]
}
}
} }

173
package-lock.json generated
View file

@ -1183,7 +1183,8 @@
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"optional": true, "optional": true,
"requires": { "requires": {
"bindings": "^1.5.0" "bindings": "^1.5.0",
"nan": "^2.12.1"
} }
}, },
"glob-parent": { "glob-parent": {
@ -1744,6 +1745,21 @@
"@turf/helpers": "^6.3.0", "@turf/helpers": "^6.3.0",
"@turf/invariant": "^6.3.0", "@turf/invariant": "^6.3.0",
"polygon-clipping": "^0.15.2" "polygon-clipping": "^0.15.2"
},
"dependencies": {
"polygon-clipping": {
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.3.tgz",
"integrity": "sha512-ho0Xx5DLkgxRx/+n4O74XyJ67DcyN3Tu9bGYKsnTukGAW6ssnuak6Mwcyb1wHy9MZc9xsUWqIoiazkZB5weECg==",
"requires": {
"splaytree": "^3.1.0"
}
},
"splaytree": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.0.tgz",
"integrity": "sha512-gvUGR7xnOy0fLKTCxDeUZYgU/I1Tdf8M/lM1Qrf8L2TIOR5ipZjGk02uYcdv0o2x7WjVRgpm3iS2clLyuVAt0Q=="
}
} }
}, },
"@turf/dissolve": { "@turf/dissolve": {
@ -1882,6 +1898,21 @@
"@turf/helpers": "^6.3.0", "@turf/helpers": "^6.3.0",
"@turf/invariant": "^6.3.0", "@turf/invariant": "^6.3.0",
"polygon-clipping": "^0.15.2" "polygon-clipping": "^0.15.2"
},
"dependencies": {
"polygon-clipping": {
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.3.tgz",
"integrity": "sha512-ho0Xx5DLkgxRx/+n4O74XyJ67DcyN3Tu9bGYKsnTukGAW6ssnuak6Mwcyb1wHy9MZc9xsUWqIoiazkZB5weECg==",
"requires": {
"splaytree": "^3.1.0"
}
},
"splaytree": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.0.tgz",
"integrity": "sha512-gvUGR7xnOy0fLKTCxDeUZYgU/I1Tdf8M/lM1Qrf8L2TIOR5ipZjGk02uYcdv0o2x7WjVRgpm3iS2clLyuVAt0Q=="
}
} }
}, },
"@turf/invariant": { "@turf/invariant": {
@ -2588,6 +2619,21 @@
"@turf/helpers": "^6.3.0", "@turf/helpers": "^6.3.0",
"@turf/invariant": "^6.3.0", "@turf/invariant": "^6.3.0",
"polygon-clipping": "^0.15.2" "polygon-clipping": "^0.15.2"
},
"dependencies": {
"polygon-clipping": {
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.3.tgz",
"integrity": "sha512-ho0Xx5DLkgxRx/+n4O74XyJ67DcyN3Tu9bGYKsnTukGAW6ssnuak6Mwcyb1wHy9MZc9xsUWqIoiazkZB5weECg==",
"requires": {
"splaytree": "^3.1.0"
}
},
"splaytree": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.0.tgz",
"integrity": "sha512-gvUGR7xnOy0fLKTCxDeUZYgU/I1Tdf8M/lM1Qrf8L2TIOR5ipZjGk02uYcdv0o2x7WjVRgpm3iS2clLyuVAt0Q=="
}
} }
}, },
"@turf/unkink-polygon": { "@turf/unkink-polygon": {
@ -7770,6 +7816,59 @@
"requires": { "requires": {
"commander": "2" "commander": "2"
} }
},
"turf": {
"version": "3.0.14",
"resolved": "https://registry.npmjs.org/turf/-/turf-3.0.14.tgz",
"integrity": "sha1-6y9KgKLVg7jGSGvHtccZBGaGbCc=",
"requires": {
"turf-along": "^3.0.12",
"turf-area": "^3.0.12",
"turf-bbox": "^3.0.12",
"turf-bbox-polygon": "^3.0.12",
"turf-bearing": "^3.0.12",
"turf-bezier": "^3.0.12",
"turf-buffer": "^3.0.12",
"turf-center": "^3.0.12",
"turf-centroid": "^3.0.12",
"turf-circle": "^3.0.12",
"turf-collect": "^3.0.12",
"turf-combine": "^3.0.12",
"turf-concave": "^3.0.12",
"turf-convex": "^3.0.12",
"turf-destination": "^3.0.12",
"turf-difference": "^3.0.12",
"turf-distance": "^3.0.12",
"turf-envelope": "^3.0.12",
"turf-explode": "^3.0.12",
"turf-flip": "^3.0.12",
"turf-helpers": "^3.0.12",
"turf-hex-grid": "^3.0.12",
"turf-inside": "^3.0.12",
"turf-intersect": "^3.0.12",
"turf-isolines": "^3.0.12",
"turf-kinks": "^3.0.12",
"turf-line-distance": "^3.0.12",
"turf-line-slice": "^3.0.12",
"turf-meta": "^3.0.12",
"turf-midpoint": "^3.0.12",
"turf-nearest": "^3.0.12",
"turf-planepoint": "^3.0.12",
"turf-point-grid": "^3.0.12",
"turf-point-on-line": "^3.0.12",
"turf-point-on-surface": "^3.0.12",
"turf-random": "^3.0.12",
"turf-sample": "^3.0.12",
"turf-simplify": "^3.0.12",
"turf-square": "^3.0.12",
"turf-square-grid": "^3.0.12",
"turf-tag": "^3.0.12",
"turf-tesselate": "^3.0.12",
"turf-tin": "^3.0.12",
"turf-triangle-grid": "^3.0.12",
"turf-union": "^3.0.12",
"turf-within": "^3.0.12"
}
} }
} }
}, },
@ -8238,6 +8337,12 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}, },
"nan": {
"version": "2.14.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
"optional": true
},
"nanoid": { "nanoid": {
"version": "3.1.22", "version": "3.1.22",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz",
@ -9084,14 +9189,6 @@
"resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz",
"integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==" "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw=="
}, },
"polygon-clipping": {
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.3.tgz",
"integrity": "sha512-ho0Xx5DLkgxRx/+n4O74XyJ67DcyN3Tu9bGYKsnTukGAW6ssnuak6Mwcyb1wHy9MZc9xsUWqIoiazkZB5weECg==",
"requires": {
"splaytree": "^3.1.0"
}
},
"posix-character-classes": { "posix-character-classes": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@ -10820,11 +10917,6 @@
"integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==",
"dev": true "dev": true
}, },
"splaytree": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.0.tgz",
"integrity": "sha512-gvUGR7xnOy0fLKTCxDeUZYgU/I1Tdf8M/lM1Qrf8L2TIOR5ipZjGk02uYcdv0o2x7WjVRgpm3iS2clLyuVAt0Q=="
},
"split": { "split": {
"version": "0.2.10", "version": "0.2.10",
"resolved": "https://registry.npmjs.org/split/-/split-0.2.10.tgz", "resolved": "https://registry.npmjs.org/split/-/split-0.2.10.tgz",
@ -11587,59 +11679,6 @@
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
}, },
"turf": {
"version": "3.0.14",
"resolved": "https://registry.npmjs.org/turf/-/turf-3.0.14.tgz",
"integrity": "sha1-6y9KgKLVg7jGSGvHtccZBGaGbCc=",
"requires": {
"turf-along": "^3.0.12",
"turf-area": "^3.0.12",
"turf-bbox": "^3.0.12",
"turf-bbox-polygon": "^3.0.12",
"turf-bearing": "^3.0.12",
"turf-bezier": "^3.0.12",
"turf-buffer": "^3.0.12",
"turf-center": "^3.0.12",
"turf-centroid": "^3.0.12",
"turf-circle": "^3.0.12",
"turf-collect": "^3.0.12",
"turf-combine": "^3.0.12",
"turf-concave": "^3.0.12",
"turf-convex": "^3.0.12",
"turf-destination": "^3.0.12",
"turf-difference": "^3.0.12",
"turf-distance": "^3.0.12",
"turf-envelope": "^3.0.12",
"turf-explode": "^3.0.12",
"turf-flip": "^3.0.12",
"turf-helpers": "^3.0.12",
"turf-hex-grid": "^3.0.12",
"turf-inside": "^3.0.12",
"turf-intersect": "^3.0.12",
"turf-isolines": "^3.0.12",
"turf-kinks": "^3.0.12",
"turf-line-distance": "^3.0.12",
"turf-line-slice": "^3.0.12",
"turf-meta": "^3.0.12",
"turf-midpoint": "^3.0.12",
"turf-nearest": "^3.0.12",
"turf-planepoint": "^3.0.12",
"turf-point-grid": "^3.0.12",
"turf-point-on-line": "^3.0.12",
"turf-point-on-surface": "^3.0.12",
"turf-random": "^3.0.12",
"turf-sample": "^3.0.12",
"turf-simplify": "^3.0.12",
"turf-square": "^3.0.12",
"turf-square-grid": "^3.0.12",
"turf-tag": "^3.0.12",
"turf-tesselate": "^3.0.12",
"turf-tin": "^3.0.12",
"turf-triangle-grid": "^3.0.12",
"turf-union": "^3.0.12",
"turf-within": "^3.0.12"
}
},
"turf-along": { "turf-along": {
"version": "3.0.12", "version": "3.0.12",
"resolved": "https://registry.npmjs.org/turf-along/-/turf-along-3.0.12.tgz", "resolved": "https://registry.npmjs.org/turf-along/-/turf-along-3.0.12.tgz",

View file

@ -9,14 +9,14 @@
"scripts": { "scripts": {
"increase-memory": "export NODE_OPTIONS=--max_old_space_size=4096", "increase-memory": "export NODE_OPTIONS=--max_old_space_size=4096",
"start": "ts-node scripts/generateLayerOverview.ts --no-fail && npm run increase-memory && parcel *.html UI/** Logic/** assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*", "start": "ts-node scripts/generateLayerOverview.ts --no-fail && npm run increase-memory && parcel *.html UI/** Logic/** assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*",
"test": "ts-node test/Tag.spec.ts && ts-node test/TagQuestion.spec.ts && ts-node test/ImageSearcher.spec.ts && ts-node test/ImageAttribution.spec.ts && ts-node test/Theme.spec.ts", "test": "ts-node test/TestAll.ts",
"init": "npm run generate && npm run generate:editor-layer-index && npm run generate:layouts && npm run clean", "init": "npm run generate && npm run generate:editor-layer-index && npm run generate:layouts && npm run clean",
"generate:editor-layer-index": "cd assets/ && wget https://osmlab.github.io/editor-layer-index/imagery.geojson --output-document=editor-layer-index.json", "generate:editor-layer-index": "cd assets/ && wget https://osmlab.github.io/editor-layer-index/imagery.geojson --output-document=editor-layer-index.json",
"generate:images": "ts-node scripts/generateIncludedImages.ts", "generate:images": "ts-node scripts/generateIncludedImages.ts",
"generate:translations": "ts-node scripts/generateTranslations.ts", "generate:translations": "ts-node scripts/generateTranslations.ts",
"generate:layouts": "ts-node scripts/generateLayouts.ts", "generate:layouts": "ts-node scripts/generateLayouts.ts",
"generate:docs": "ts-node scripts/generateDocs.ts && ts-node scripts/generateTaginfoProjectFiles.ts", "generate:docs": "ts-node scripts/generateDocs.ts && ts-node scripts/generateTaginfoProjectFiles.ts",
"generate:cache:speelplekken": "ts-node scripts/generateCache.ts speelplekken 14 ../pietervdvn.github.io/speelplekken_cache/ 51.22 4.30 51.08 4.55", "generate:cache:speelplekken": "npm run generate:layeroverview && ts-node scripts/generateCache.ts speelplekken 14 ../pietervdvn.github.io/speelplekken_cache/ 51.20 4.35 51.09 4.56",
"generate:layeroverview": "ts-node scripts/generateLayerOverview.ts --no-fail", "generate:layeroverview": "ts-node scripts/generateLayerOverview.ts --no-fail",
"generate:licenses": "ts-node scripts/generateLicenseInfo.ts --no-fail", "generate:licenses": "ts-node scripts/generateLicenseInfo.ts --no-fail",
"generate:report": "cd Docs/Tools && ./compileStats.sh && git commit . -m 'New statistics ands graphs' && git push", "generate:report": "cd Docs/Tools && ./compileStats.sh && git commit . -m 'New statistics ands graphs' && git push",
@ -79,8 +79,7 @@
"sharp": "^0.27.0", "sharp": "^0.27.0",
"slick-carousel": "^1.8.1", "slick-carousel": "^1.8.1",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2", "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2",
"tslint": "^6.1.3", "tslint": "^6.1.3"
"turf": "^3.0.14"
}, },
"devDependencies": { "devDependencies": {
"@babel/polyfill": "^7.10.4", "@babel/polyfill": "^7.10.4",

View file

@ -18,9 +18,9 @@ export default class ScriptUtils {
return result; return result;
} }
public static DownloadJSON(url, continuation : (parts : string []) => void){ public static DownloadJSON(url) : Promise<any>{
return new Promise((resolve, reject) => {
https.get(url, (res) => { https.get(url, (res) => {
console.log("Got response!")
const parts : string[] = [] const parts : string[] = []
res.setEncoding('utf8'); res.setEncoding('utf8');
res.on('data', function (chunk) { res.on('data', function (chunk) {
@ -29,17 +29,27 @@ export default class ScriptUtils {
}); });
res.addListener('end', function () { res.addListener('end', function () {
continuation(parts) const result = parts.join("")
try{
resolve(JSON.parse(result))
}catch (e){
reject(e)
}
}); });
}) })
})
} }
public static sleep(ms) { public static sleep(ms) {
if(ms <= 0){
process.stdout.write("\r \r")
return;
}
return new Promise((resolve) => { return new Promise((resolve) => {
console.debug("Sleeping for", ms) process.stdout.write("\r Sleeping for "+(ms/1000)+"s \r")
setTimeout(resolve, ms); setTimeout(resolve, 1000);
}).then(() => ScriptUtils.sleep(ms - 1000));
});
} }

View file

@ -16,6 +16,7 @@ import * as OsmToGeoJson from "osmtogeojson";
import MetaTagging from "../Logic/MetaTagging"; import MetaTagging from "../Logic/MetaTagging";
import LayerConfig from "../Customizations/JSON/LayerConfig"; import LayerConfig from "../Customizations/JSON/LayerConfig";
import {GeoOperations} from "../Logic/GeoOperations"; import {GeoOperations} from "../Logic/GeoOperations";
import {fail} from "assert";
function createOverpassObject(theme: LayoutConfig) { function createOverpassObject(theme: LayoutConfig) {
@ -29,8 +30,11 @@ function createOverpassObject(theme: LayoutConfig) {
continue; continue;
} }
if (layer.source.geojsonSource !== undefined) { if (layer.source.geojsonSource !== undefined) {
// We download these anyway - we are building the cache after all! // This layer defines a geoJson-source
//continue; // SHould it be cached?
if (!layer.source.isOsmCacheLayer) {
continue;
}
} }
@ -49,17 +53,6 @@ function createOverpassObject(theme: LayoutConfig) {
return new Overpass(new Or(filters), extraScripts); return new Overpass(new Or(filters), extraScripts);
} }
function saveResponse(chunks: string[], targetDir: string): boolean {
const contents = chunks.join("")
if (contents.startsWith("<?xml")) {
// THis is an error message
console.error("Failed to create ", targetDir, "probably over quota: ", contents)
return false;
}
writeFileSync(targetDir, contents)
return true
}
function rawJsonName(targetDir: string, x: number, y: number, z: number): string { function rawJsonName(targetDir: string, x: number, y: number, z: number): string {
return targetDir + "_" + z + "_" + x + "_" + y + ".json" return targetDir + "_" + z + "_" + x + "_" + y + ".json"
} }
@ -68,6 +61,7 @@ function geoJsonName(targetDir: string, x: number, y: number, z: number): string
return targetDir + "_" + z + "_" + x + "_" + y + ".geojson" return targetDir + "_" + z + "_" + x + "_" + y + ".geojson"
} }
/// Downloads the given feature and saves them to disk
async function downloadRaw(targetdir: string, r: TileRange, overpass: Overpass)/* : {failed: number, skipped :number} */ { async function downloadRaw(targetdir: string, r: TileRange, overpass: Overpass)/* : {failed: number, skipped :number} */ {
let downloaded = 0 let downloaded = 0
let failed = 0 let failed = 0
@ -92,37 +86,48 @@ async function downloadRaw(targetdir: string, r: TileRange, overpass: Overpass)/
} }
const url = overpass.buildQuery("[bbox:" + bounds.south + "," + bounds.west + "," + bounds.north + "," + bounds.east + "]") const url = overpass.buildQuery("[bbox:" + bounds.south + "," + bounds.west + "," + bounds.north + "," + bounds.east + "]")
let gotResponse = false await ScriptUtils.DownloadJSON(url)
let success = false; .then(json => {
ScriptUtils.DownloadJSON(url, console.log("Got the response - writing to ", filename)
chunks => { writeFileSync(filename, JSON.stringify(json, null, " "));
gotResponse = true;
success = saveResponse(chunks, filename)
})
while (!gotResponse) {
await ScriptUtils.sleep(10000)
console.debug("Waking up")
if (!gotResponse) {
console.log("Didn't get an answer yet - waiting more")
} }
} )
.catch(err => {
if (!success) { console.log("Could not download - probably hit the rate limit; waiting a bit")
failed++; failed++;
console.log("Hit the rate limit - waiting 90s") return ScriptUtils.sleep(60000).then(() => console.log("Waiting is done"))
for (let i = 0; i < 90; i++) { })
console.log(90 - i) // Cooldown
await ScriptUtils.sleep(1000) console.debug("Cooling down 10s")
} await ScriptUtils.sleep(10000)
}
} }
} }
return {failed: failed, skipped: skipped} return {failed: failed, skipped: skipped}
} }
async function postProcess(targetdir: string, r: TileRange, theme: LayoutConfig) { /*
* Downloads extra geojson sources and returns the features.
* Extra geojson layers should not be tiled
*/
async function downloadExtraData(theme: LayoutConfig)/* : any[] */ {
const allFeatures: any[] = []
for (const layer of theme.layers) {
const source = layer.source.geojsonSource;
if (source === undefined) {
continue;
}
if (layer.source.isOsmCacheLayer) {
// Cached layers are not considered here
continue;
}
console.log("Downloading extra data: ", source)
await ScriptUtils.DownloadJSON(source).then(json => allFeatures.push(...json.features))
}
return allFeatures;
}
async function postProcess(targetdir: string, r: TileRange, theme: LayoutConfig, extraFeatures: any[]) {
let processed = 0; let processed = 0;
const layerIndex = theme.LayerIndex(); const layerIndex = theme.LayerIndex();
for (let x = r.xstart; x <= r.xend; x++) { for (let x = r.xstart; x <= r.xend; x++) {
@ -131,7 +136,8 @@ async function postProcess(targetdir: string, r: TileRange, theme: LayoutConfig)
const filename = rawJsonName(targetdir, x, y, r.zoomlevel) const filename = rawJsonName(targetdir, x, y, r.zoomlevel)
console.log(" Post processing", processed, "/", r.total, filename) console.log(" Post processing", processed, "/", r.total, filename)
if (!existsSync(filename)) { if (!existsSync(filename)) {
throw "Not found - and not downloaded. Run this script again!: " + filename console.error("Not found - and not downloaded. Run this script again!: " + filename)
continue;
} }
// We read the raw OSM-file and convert it to a geojson // We read the raw OSM-file and convert it to a geojson
@ -140,6 +146,8 @@ async function postProcess(targetdir: string, r: TileRange, theme: LayoutConfig)
// Create and save the geojson file - which is the main chunk of the data // Create and save the geojson file - which is the main chunk of the data
const geojson = OsmToGeoJson.default(rawOsm); const geojson = OsmToGeoJson.default(rawOsm);
const osmTime = new Date(rawOsm.osm3s.timestamp_osm_base); const osmTime = new Date(rawOsm.osm3s.timestamp_osm_base);
// And merge in the extra features - needed for the metatagging
geojson.features.push(...extraFeatures);
for (const feature of geojson.features) { for (const feature of geojson.features) {
@ -149,9 +157,6 @@ async function postProcess(targetdir: string, r: TileRange, theme: LayoutConfig)
break; break;
} }
} }
if(feature["_matching_layer_id"] === undefined){
console.log("No matching layer found for ", feature.properties.id)
}
} }
const featuresFreshness = geojson.features.map(feature => { const featuresFreshness = geojson.features.map(feature => {
return ({ return ({
@ -161,6 +166,7 @@ async function postProcess(targetdir: string, r: TileRange, theme: LayoutConfig)
}); });
// Extract the relationship information // Extract the relationship information
const relations = ExtractRelations.BuildMembershipTable(ExtractRelations.GetRelationElements(rawOsm)) const relations = ExtractRelations.BuildMembershipTable(ExtractRelations.GetRelationElements(rawOsm))
MetaTagging.addMetatags(featuresFreshness, relations, theme.layers, false); MetaTagging.addMetatags(featuresFreshness, relations, theme.layers, false);
@ -180,9 +186,14 @@ async function postProcess(targetdir: string, r: TileRange, theme: LayoutConfig)
} }
} }
for (const feature of geojson.features) {
// Some cleanup
delete feature["bbox"]
}
const targetPath = geoJsonName(targetdir+".unfiltered", x, y, r.zoomlevel)
writeFileSync(geoJsonName(targetdir, x, y, r.zoomlevel), JSON.stringify(geojson, null, " ")) // This is the geojson file containing all features
writeFileSync(targetPath, JSON.stringify(geojson, null, " "))
} }
} }
@ -192,15 +203,30 @@ async function splitPerLayer(targetdir: string, r: TileRange, theme: LayoutConfi
const z = r.zoomlevel; const z = r.zoomlevel;
for (let x = r.xstart; x <= r.xend; x++) { for (let x = r.xstart; x <= r.xend; x++) {
for (let y = r.ystart; y <= r.yend; y++) { for (let y = r.ystart; y <= r.yend; y++) {
const file = readFileSync(geoJsonName(targetdir, x, y, z), "UTF8") const file = readFileSync(geoJsonName(targetdir+".unfiltered", x, y, z), "UTF8")
for (const layer of theme.layers) { for (const layer of theme.layers) {
const geojson = JSON.parse(file) if (!layer.source.isOsmCacheLayer) {
geojson.features = geojson.features.filter(f => f._matching_layer_id === layer.id)
if (geojson.features.length == 0) {
continue; continue;
} }
const geojson = JSON.parse(file)
const oldLength = geojson.features.length;
geojson.features = geojson.features
.filter(f => f._matching_layer_id === layer.id)
.filter(f => {
const isShown = layer.isShown.GetRenderValue(f.properties).txt
if (isShown === "no") {
console.log("Dropping feature ", f.id)
return false;
}
return true;
})
const new_path = geoJsonName(targetdir + "_" + layer.id, x, y, z); const new_path = geoJsonName(targetdir + "_" + layer.id, x, y, z);
console.log(new_path, " has ", geojson.features.length, " features after filtering (dropped ", oldLength - geojson.features.length,")" )
if (geojson.features.length == 0) {
console.log("Not writing geojson file as it is empty", new_path)
continue;
}
writeFileSync(new_path, JSON.stringify(geojson, null, " ")) writeFileSync(new_path, JSON.stringify(geojson, null, " "))
} }
@ -243,11 +269,12 @@ async function main(args: string[]) {
const cachingResult = await downloadRaw(targetdir, tileRange, overpass) const cachingResult = await downloadRaw(targetdir, tileRange, overpass)
failed = cachingResult.failed failed = cachingResult.failed
if (failed > 0) { if (failed > 0) {
ScriptUtils.sleep(30000) await ScriptUtils.sleep(30000)
} }
} while (failed > 0) } while (failed > 0)
await postProcess(targetdir, tileRange, theme) const extraFeatures = await downloadExtraData(theme);
await postProcess(targetdir, tileRange, theme, extraFeatures)
await splitPerLayer(targetdir, tileRange, theme) await splitPerLayer(targetdir, tileRange, theme)
} }

View file

@ -32,8 +32,13 @@ const themeFiles: any[] = ScriptUtils.readDirRecSync("./assets/themes")
.filter(path => path.endsWith(".json")) .filter(path => path.endsWith(".json"))
.filter(path => path.indexOf("license_info.json") < 0) .filter(path => path.indexOf("license_info.json") < 0)
.map(path => { .map(path => {
try{
return JSON.parse(readFileSync(path, "UTF8")); return JSON.parse(readFileSync(path, "UTF8"));
}) }catch(e){
console.error("Could not read file ", path, "due to ", e)
throw e
}
});
console.log("Discovered", layerFiles.length, "layers and", themeFiles.length, "themes\n") console.log("Discovered", layerFiles.length, "layers and", themeFiles.length, "themes\n")
return { return {

197
test/GeoOperations.spec.ts Normal file
View file

@ -0,0 +1,197 @@
import {Utils} from "../Utils";
Utils.runningFromConsole = true;
import {equal} from "assert";
import T from "./TestHelper";
import {FromJSON} from "../Customizations/JSON/FromJSON";
import Locale from "../UI/i18n/Locale";
import Translations from "../UI/i18n/Translations";
import {UIEventSource} from "../Logic/UIEventSource";
import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig";
import EditableTagRendering from "../UI/Popup/EditableTagRendering";
import {Translation} from "../UI/i18n/Translation";
import {OH, OpeningHour} from "../UI/OpeningHours/OpeningHours";
import PublicHolidayInput from "../UI/OpeningHours/PublicHolidayInput";
import {SubstitutedTranslation} from "../UI/SubstitutedTranslation";
import {Tag} from "../Logic/Tags/Tag";
import {And} from "../Logic/Tags/And";
import * as Assert from "assert";
import {GeoOperations} from "../Logic/GeoOperations";
export default class GeoOperationsSpec extends T {
private static polygon = {
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
1.8017578124999998,
50.401515322782366
],
[
-3.1640625,
46.255846818480315
],
[
5.185546875,
44.74673324024678
],
[
1.8017578124999998,
50.401515322782366
]
]
]
}
};
private static multiPolygon = {
"type": "Feature",
"properties": {},
"geometry": {
"type": "MultiPolygon",
"coordinates": [[
[
[
1.8017578124999998,
50.401515322782366
],
[
-3.1640625,
46.255846818480315
],
[
5.185546875,
44.74673324024678
],
[
1.8017578124999998,
50.401515322782366
]
],
[
[
1.0107421875,
48.821332549646634
],
[
1.329345703125,
48.25394114463431
],
[
1.988525390625,
48.71271258145237
],
[
0.999755859375,
48.86471476180277
],
[
1.0107421875,
48.821332549646634
]
]
]]
}
};
private static inHole = [1.42822265625, 48.61838518688487]
private static inMultiPolygon = [2.515869140625, 47.37603463349758]
private static outsidePolygon = [4.02099609375, 47.81315451752768]
constructor() {
super(
"GeoOperationsSpec", [
["Point out of polygon", () => {
GeoOperationsSpec.isFalse(GeoOperations.inside([
3.779296875,
48.777912755501845
], GeoOperationsSpec.polygon), "Point is outside of the polygon");
}
],
["Point inside of polygon", () => {
GeoOperationsSpec.isTrue(GeoOperations.inside([
1.23046875,
47.60616304386874
], GeoOperationsSpec.polygon), "Point is inside of the polygon");
}
],
["MultiPolygonTest", () => {
const isTrue = GeoOperationsSpec.isTrue;
const isFalse = GeoOperationsSpec.isFalse;
isFalse(GeoOperations.inside(GeoOperationsSpec.inHole, GeoOperationsSpec.multiPolygon), "InHole was detected as true");
isTrue(GeoOperations.inside(GeoOperationsSpec.inMultiPolygon, GeoOperationsSpec.multiPolygon), "InMultiPolgyon was not detected as true");
isFalse(GeoOperations.inside(GeoOperationsSpec.outsidePolygon, GeoOperationsSpec.multiPolygon), "OutsideOfPolygon was detected as true");
}],
["Intersection between a line and a polygon", () => {
const line = {
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[
3.779296875,
48.777912755501845
],
[
1.23046875,
47.60616304386874
]
]
}
};
const overlap = GeoOperations.calculateOverlap(line, [GeoOperationsSpec.polygon]);
Assert.equal(1, overlap.length)
}],
["Fully enclosed", () => {
const line = {
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[
0.0439453125,
47.31648293428332
],
[
0.6591796875,
46.77749276376827
]
]
}
};
const overlap = GeoOperations.calculateOverlap(line, [GeoOperationsSpec.polygon]);
Assert.equal(1, overlap.length)
}],
["overlapWith matches points too", () => {
const point = {
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
2.274169921875,
46.76244305208004
]
}
};
const overlap = GeoOperations.calculateOverlap(point, [GeoOperationsSpec.polygon]);
Assert.equal(1, overlap.length)
}]
]
)
}
}

View file

@ -20,8 +20,10 @@ import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
import AllKnownLayers from "../Customizations/AllKnownLayers"; import AllKnownLayers from "../Customizations/AllKnownLayers";
import LayerConfig from "../Customizations/JSON/LayerConfig"; import LayerConfig from "../Customizations/JSON/LayerConfig";
export default class ImageAttributionSpec extends T {
new T("ImageAttribution Tests", [ constructor() {
super(
"ImageAttribution Tests", [
[ [
"Should find all the images", "Should find all the images",
() => { () => {
@ -55,4 +57,6 @@ new T("ImageAttribution Tests", [
] ]
]) ]);
}
}

View file

@ -16,9 +16,10 @@ import {SubstitutedTranslation} from "../UI/SubstitutedTranslation";
import {Tag} from "../Logic/Tags/Tag"; import {Tag} from "../Logic/Tags/Tag";
import {And} from "../Logic/Tags/And"; import {And} from "../Logic/Tags/And";
import {ImageSearcher} from "../Logic/Actors/ImageSearcher"; import {ImageSearcher} from "../Logic/Actors/ImageSearcher";
export default class ImageSearcherSpec extends T {
constructor() {
new T("ImageSearcher", [ super("ImageSearcher", [
[ [
"Should find images", "Should find images",
() => { () => {
@ -32,4 +33,9 @@ new T("ImageSearcher", [
] ]
]) ]);
}
}

View file

@ -1,6 +1,4 @@
import {Utils} from "../Utils"; import {Utils} from "../Utils";
Utils.runningFromConsole = true;
import {equal} from "assert"; import {equal} from "assert";
import T from "./TestHelper"; import T from "./TestHelper";
import {FromJSON} from "../Customizations/JSON/FromJSON"; import {FromJSON} from "../Customizations/JSON/FromJSON";
@ -15,10 +13,13 @@ import PublicHolidayInput from "../UI/OpeningHours/PublicHolidayInput";
import {SubstitutedTranslation} from "../UI/SubstitutedTranslation"; import {SubstitutedTranslation} from "../UI/SubstitutedTranslation";
import {Tag} from "../Logic/Tags/Tag"; import {Tag} from "../Logic/Tags/Tag";
import {And} from "../Logic/Tags/And"; import {And} from "../Logic/Tags/And";
import * as Assert from "assert";
Utils.runningFromConsole = true;
new T("Tags", [ export default class TagSpec extends T{
constructor() {
super("Tags", [
["Tag replacement works in translation", () => { ["Tag replacement works in translation", () => {
const tr = new Translation({ const tr = new Translation({
"en": "Test {key} abc" "en": "Test {key} abc"
@ -426,3 +427,6 @@ new T("Tags", [
equal(true, tagRendering.IsKnown({bottle: "yes"})) equal(true, tagRendering.IsKnown({bottle: "yes"}))
equal(false, tagRendering.IsKnown({})) equal(false, tagRendering.IsKnown({}))
}]]); }]]);
}
}

View file

@ -7,8 +7,9 @@ import {UIEventSource} from "../Logic/UIEventSource";
import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig"; import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig";
import EditableTagRendering from "../UI/Popup/EditableTagRendering"; import EditableTagRendering from "../UI/Popup/EditableTagRendering";
export default class TagQuestionSpec extends T {
new T("TagQuestionElement", constructor() {
super("TagQuestionElement",
[ [
["Freeform has textfield", () => { ["Freeform has textfield", () => {
const tags = new UIEventSource({ const tags = new UIEventSource({
@ -44,8 +45,10 @@ new T("TagQuestionElement",
type: "string" type: "string"
}, },
mappings: [ mappings: [
{"if": "noname=yes", {
"then": "This bookcase has no name"} "if": "noname=yes",
"then": "This bookcase has no name"
}
] ]
}, undefined, "Testing tag" }, undefined, "Testing tag"
); );
@ -55,5 +58,6 @@ new T("TagQuestionElement",
T.assertContains("This bookcase has no name", html); T.assertContains("This bookcase has no name", html);
T.assertContains("<input type='text'", html); T.assertContains("<input type='text'", html);
}] }]
] ]);
); }
}

27
test/TestAll.ts Normal file
View file

@ -0,0 +1,27 @@
import {Utils} from "../Utils";
Utils.runningFromConsole = true;
import TagSpec from "./Tag.spec";
import ImageAttributionSpec from "./ImageAttribution.spec";
import GeoOperationsSpec from "./GeoOperations.spec";
import TagQuestionSpec from "./TagQuestion.spec";
import ImageSearcherSpec from "./ImageSearcher.spec";
import ThemeSpec from "./Theme.spec";
import UtilsSpec from "./Utils.spec";
const allTests = [
new TagSpec(),
new ImageAttributionSpec(),
new GeoOperationsSpec(),
new TagQuestionSpec(),
new ImageSearcherSpec(),
new ThemeSpec(),
new UtilsSpec()]
for (const test of allTests) {
if(test.failures.length > 0){
throw "Some test failed"
}
}

View file

@ -1,33 +1,38 @@
export default class T { export default class T {
constructor(testsuite: string, tests: [string, () => void ][]) { public readonly failures = []
let failures : string []= [];
constructor(testsuite: string, tests: [string, () => void][]) {
for (const [name, test] of tests) { for (const [name, test] of tests) {
try { try {
test(); test();
} catch (e) { } catch (e) {
failures.push(name); this.failures.push(name);
console.warn("Failed test: ", name, "because", e); console.warn("Failed test: ", name, "because", e);
} }
} }
if (failures.length == 0) { if (this.failures.length == 0) {
console.log(`All tests of ${testsuite} done!`) console.log(`All tests of ${testsuite} done!`)
} else { } else {
console.warn(failures.length, `tests of ${testsuite} failed :(`) console.warn(this.failures.length, `tests of ${testsuite} failed :(`)
console.log("Failed tests: ", failures.join(",")) console.log("Failed tests: ", this.failures.join(","))
} }
} }
static assertContains(needle: string, actual: string){ static assertContains(needle: string, actual: string) {
if(actual.indexOf(needle) < 0){ if (actual.indexOf(needle) < 0) {
throw `The substring ${needle} was not found` throw `The substring ${needle} was not found`
} }
} }
static isTrue(b: boolean, msg: string) { static isTrue(b: boolean, msg: string) {
if(!b){ if (!b) {
throw "Expected true, but got false: "+msg throw "Expected true, but got false: " + msg
}
}
static isFalse(b: boolean, msg: string) {
if (b) {
throw "Expected false, but got true: " + msg
} }
} }
} }

View file

@ -9,8 +9,9 @@ import LayoutConfig from "../Customizations/JSON/LayoutConfig";
import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson"; import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson";
import * as assert from "assert"; import * as assert from "assert";
export default class ThemeSpec extends T{
new T("Theme tests", constructor() {
super("Theme tests",
[ [
["Nested overrides work", () => { ["Nested overrides work", () => {
@ -44,5 +45,6 @@ new T("Theme tests",
}] }]
] ]);
); }
}

View file

@ -1,11 +1,47 @@
import T from "./TestHelper"; import T from "./TestHelper";
import {Utils} from "../Utils"; import {Utils} from "../Utils";
import {equal} from "assert"; import {equal} from "assert";
import {existsSync, mkdirSync, readFileSync, writeFile, writeFileSync} from "fs";
import LZString from "lz-string"; import LZString from "lz-string";
new T("Utils",[
["Minify-json",() => { export default class UtilsSpec extends T {
const str = JSON.stringify({title: "abc", "and":"xyz", "render":"somevalue"}, null, 0); private static readonly example = {
"id": "bookcases",
"maintainer": "MapComplete",
"version": "2020-08-29",
"language": [
"en",
"nl",
"de",
"fr"
],
"title": {
"en": "Open Bookcase Map",
"nl": "Open Boekenruilkastenkaart",
"de": "Öffentliche Bücherschränke Karte",
"fr": "Carte des microbibliothèques"
},
"description": {
"en": "A public bookcase is a small streetside cabinet, box, old phone boot or some other objects where books are stored. Everyone can place or take a book. This map aims to collect all these bookcases. You can discover new bookcases nearby and, with a free OpenStreetMap account, quickly add your favourite bookcases.",
"nl": "Een boekenruilkast is een kastje waar iedereen een boek kan nemen of achterlaten. Op deze kaart kan je deze boekenruilkasten terugvinden en met een gratis OpenStreetMap-account, ook boekenruilkasten toevoegen of informatie verbeteren",
"de": "Ein öffentlicher Bücherschrank ist ein kleiner Bücherschrank am Straßenrand, ein Kasten, eine alte Telefonzelle oder andere Gegenstände, in denen Bücher aufbewahrt werden. Jeder kann ein Buch hinstellen oder mitnehmen. Diese Karte zielt darauf ab, all diese Bücherschränke zu sammeln. Sie können neue Bücherschränke in der Nähe entdecken und mit einem kostenlosen OpenStreetMap-Account schnell Ihre Lieblingsbücherschränke hinzufügen.",
"fr": "Une microbibliothèques, également appelée boite à livre, est un élément de mobilier urbain (étagère, armoire, etc) dans lequel sont stockés des livres et autres objets en accès libre. Découvrez les boites à livres prêt de chez vous, ou ajouter en une nouvelle à l'aide de votre compte OpenStreetMap."
},
"icon": "./assets/themes/bookcases/bookcase.svg",
"socialImage": null,
"startLat": 0,
"startLon": 0,
"startZoom": 1,
"widenFactor": 0.05,
"roamingRenderings": [],
"layers": [
"public_bookcase"
]
}
constructor() {
super("Utils", [
["Minify-json", () => {
const str = JSON.stringify({title: "abc", "and": "xyz", "render": "somevalue"}, null, 0);
const minified = Utils.MinifyJSON(str); const minified = Utils.MinifyJSON(str);
console.log(minified) console.log(minified)
console.log("Minified version has ", minified.length, "chars") console.log("Minified version has ", minified.length, "chars")
@ -15,9 +51,8 @@ new T("Utils",[
equal(str, restored) equal(str, restored)
}], }],
["Minify-json of the bookcases",() => { ["Minify-json of the bookcases", () => {
let str = readFileSync("/home/pietervdvn/git/MapComplete/assets/layers/public_bookcases/public_bookcases.json", "UTF8") const str = JSON.stringify(UtilsSpec.example, null, 0)
str = JSON.stringify(JSON.parse(str), null, 0)
const minified = Utils.MinifyJSON(str); const minified = Utils.MinifyJSON(str);
console.log("Minified version has ", minified.length, "chars") console.log("Minified version has ", minified.length, "chars")
const restored = Utils.UnMinify(minified) const restored = Utils.UnMinify(minified)
@ -25,26 +60,25 @@ new T("Utils",[
equal(str, restored) equal(str, restored)
}], }],
["Minify-json with LZ-string of the bookcases",() => { ["Minify-json with LZ-string of the bookcases", () => {
let str = readFileSync("/home/pietervdvn/git/MapComplete/assets/layers/public_bookcases/public_bookcases.json", "UTF8") const str = JSON.stringify(UtilsSpec.example, null, 0)
str = JSON.stringify(JSON.parse(str), null, 0) const minified = LZString.compressToBase64(Utils.MinifyJSON(str));
const minified =LZString.compressToBase64(Utils.MinifyJSON(str));
console.log("Minified version has ", minified.length, "chars") console.log("Minified version has ", minified.length, "chars")
const restored = Utils.UnMinify(LZString.decompressFromBase64(minified)) const restored = Utils.UnMinify(LZString.decompressFromBase64(minified))
console.log("Restored version has ", restored.length, "chars") console.log("Restored version has ", restored.length, "chars")
equal(str, restored) equal(str, restored)
}], }],
["Minify-json with only LZ-string of the bookcases",() => { ["Minify-json with only LZ-string of the bookcases", () => {
let str = readFileSync("/home/pietervdvn/git/MapComplete/assets/layers/public_bookcases/public_bookcases.json", "UTF8") const str = JSON.stringify(UtilsSpec.example, null, 0)
str = JSON.stringify(JSON.parse(str), null, 0) const minified = LZString.compressToBase64(str);
const minified =LZString.compressToBase64(str);
console.log("Minified version has ", minified.length, "chars") console.log("Minified version has ", minified.length, "chars")
const restored = LZString.decompressFromBase64(minified) const restored = LZString.decompressFromBase64(minified)
console.log("Restored version has ", restored.length, "chars") console.log("Restored version has ", restored.length, "chars")
equal(str, restored) equal(str, restored)
}] }]
]);
}
}
])