forked from MapComplete/MapComplete
Do not show out-of-range features on speelplekken layer, fix handling of mutlipolygons in 'inside', better tests
This commit is contained in:
parent
117b0bddb1
commit
6f457a6f0d
26 changed files with 1284 additions and 770 deletions
|
@ -87,7 +87,8 @@ export default class LayerConfig {
|
|||
geojsonSource: json.source["geoJson"],
|
||||
geojsonSourceLevel: json.source["geoJsonZoomLevel"],
|
||||
overpassScript: json.source["overpassScript"],
|
||||
});
|
||||
isOsmCache: json.source["isOsmCache"]
|
||||
}, this.id);
|
||||
} else {
|
||||
this.source = new SourceConfig({
|
||||
osmTags: legacy
|
||||
|
|
|
@ -30,20 +30,34 @@ export interface LayerConfigJson {
|
|||
* This determines where the data for the layer is fetched.
|
||||
* 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
|
||||
* source: {geoJson: "https://my.source.net/some-geo-data.geojson"} to fetch a geojson from a third party 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
|
||||
* # Query OSM directly
|
||||
* 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 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_.
|
||||
* 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...
|
||||
*
|
||||
*
|
||||
* # 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"}
|
||||
* While still supported, this is considered deprecated
|
||||
*/
|
||||
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 }
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,13 +6,15 @@ export default class SourceConfig {
|
|||
overpassScript?: string;
|
||||
geojsonSource?: string;
|
||||
geojsonZoomLevel?: number;
|
||||
isOsmCacheLayer: boolean;
|
||||
|
||||
constructor(params: {
|
||||
osmTags?: TagsFilter,
|
||||
overpassScript?: string,
|
||||
geojsonSource?: string,
|
||||
isOsmCache?: boolean,
|
||||
geojsonSourceLevel?: number
|
||||
}) {
|
||||
}, context?: string) {
|
||||
|
||||
let defined = 0;
|
||||
if (params.osmTags) {
|
||||
|
@ -27,9 +29,14 @@ export default class SourceConfig {
|
|||
if (defined == 0) {
|
||||
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.overpassScript = params.overpassScript;
|
||||
this.geojsonSource = params.geojsonSource;
|
||||
this.geojsonZoomLevel = params.geojsonSourceLevel;
|
||||
this.isOsmCacheLayer = params.isOsmCache ?? false;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ import {FixedUiElement} from "./UI/Base/FixedUiElement";
|
|||
import CheckBox from "./UI/Input/CheckBox";
|
||||
import {Basemap} from "./UI/BigComponents/Basemap";
|
||||
import State from "./State";
|
||||
import LoadFromOverpass from "./Logic/Actors/UpdateFromOverpass";
|
||||
import LoadFromOverpass from "./Logic/Actors/OverpassFeatureSource";
|
||||
import {UIEventSource} from "./Logic/UIEventSource";
|
||||
import {QueryParameters} from "./Logic/Web/QueryParameters";
|
||||
import StrayClickHandler from "./Logic/Actors/StrayClickHandler";
|
||||
|
|
|
@ -10,9 +10,9 @@ import {TagsFilter} from "../Tags/TagsFilter";
|
|||
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
|
|
@ -84,7 +84,6 @@ export class ElementStorage {
|
|||
}
|
||||
}
|
||||
if (somethingChanged) {
|
||||
console.trace(`Merging multiple instances of ${elementId}: ` + debug_msg.join(", ")+" newProperties: ", newProperties)
|
||||
es.ping();
|
||||
}
|
||||
return es;
|
||||
|
|
|
@ -38,7 +38,7 @@ Some advanced functions are available on <b>feat</b> as well:
|
|||
`
|
||||
private static readonly OverlapFunc = new ExtraFunction(
|
||||
"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)"],
|
||||
(params, feat) => {
|
||||
return (...layerIds: string[]) => {
|
||||
|
|
|
@ -6,8 +6,7 @@ export class GeoOperations {
|
|||
return turf.area(feature);
|
||||
}
|
||||
|
||||
static centerpoint(feature: any)
|
||||
{
|
||||
static centerpoint(feature: any) {
|
||||
const newFeature = turf.center(feature);
|
||||
newFeature.properties = feature.properties;
|
||||
newFeature.id = feature.id;
|
||||
|
@ -33,28 +32,92 @@ export class GeoOperations {
|
|||
* 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²
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
static calculateOverlap(feature: any,
|
||||
otherFeatures: any[]): { feat: any, overlap: number }[] {
|
||||
static calculateOverlap(feature: any, otherFeatures: any[]): { feat: any, overlap: number }[] {
|
||||
|
||||
const featureBBox = BBox.get(feature);
|
||||
const result: { feat: any, overlap: number }[] = [];
|
||||
if (feature.geometry.type === "Point") {
|
||||
const coor = feature.geometry.coordinates;
|
||||
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);
|
||||
if (!featureBBox.overlapsWith(otherFeatureBBox)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.inside(coor, otherFeatures)) {
|
||||
result.push({ feat: otherFeatures, overlap: undefined })
|
||||
if (this.inside(coor, otherFeature)) {
|
||||
result.push({feat: otherFeature, overlap: undefined})
|
||||
}
|
||||
}
|
||||
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") {
|
||||
|
||||
for (const otherFeature of otherFeatures) {
|
||||
|
@ -74,7 +137,7 @@ export class GeoOperations {
|
|||
const intersectionSize = turf.area(intersection); // in m²
|
||||
result.push({feat: otherFeature, overlap: intersectionSize})
|
||||
} 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;
|
||||
}
|
||||
|
||||
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 y: number = pointCoordinate[1];
|
||||
|
@ -148,29 +237,6 @@ class BBox{
|
|||
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) {
|
||||
if (feature.bbox?.overlapsWith === undefined) {
|
||||
|
||||
|
@ -195,4 +261,27 @@ class 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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -26,7 +26,6 @@ export default class MetaTagging {
|
|||
layers: LayerConfig[],
|
||||
includeDates = true) {
|
||||
|
||||
console.debug("Adding meta tags to all features")
|
||||
for (const metatag of SimpleMetaTagger.metatags) {
|
||||
if (metatag.includesDates && !includeDates) {
|
||||
// We do not add dated entries
|
||||
|
@ -95,9 +94,17 @@ export default class MetaTagging {
|
|||
|
||||
const f = (featuresPerLayer, feature: any) => {
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ export class RegexTag extends TagsFilter {
|
|||
continue;
|
||||
}
|
||||
if (RegexTag.doesMatch(key, this.key)) {
|
||||
const value = tags[key]
|
||||
const value = tags[key] ?? "";
|
||||
return RegexTag.doesMatch(value, this.value) != this.invert;
|
||||
}
|
||||
}
|
||||
|
|
4
State.ts
4
State.ts
|
@ -13,7 +13,7 @@ import BaseLayer from "./Models/BaseLayer";
|
|||
import Loc from "./Models/Loc";
|
||||
import Constants from "./Models/Constants";
|
||||
|
||||
import UpdateFromOverpass from "./Logic/Actors/UpdateFromOverpass";
|
||||
import OverpassFeatureSource from "./Logic/Actors/OverpassFeatureSource";
|
||||
import LayerConfig from "./Customizations/JSON/LayerConfig";
|
||||
import TitleHandler from "./Logic/Actors/TitleHandler";
|
||||
import PendingChangesUploader from "./Logic/Actors/PendingChangesUploader";
|
||||
|
@ -57,7 +57,7 @@ export default class State {
|
|||
|
||||
public favouriteLayers: UIEventSource<string[]>;
|
||||
|
||||
public layerUpdater: UpdateFromOverpass;
|
||||
public layerUpdater: OverpassFeatureSource;
|
||||
|
||||
public osmApiFeatureSource : OsmApiFeatureSource ;
|
||||
|
||||
|
|
|
@ -40,11 +40,15 @@
|
|||
"override": {
|
||||
"source": {
|
||||
"geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{z}_{x}_{y}.geojson",
|
||||
"geoJsonZoomLevel": 14
|
||||
"geoJsonZoomLevel": 14,
|
||||
"isOsmCache": true
|
||||
},
|
||||
"icon": "./assets/themes/speelplekken/speelbos.svg",
|
||||
"minzoom": 12
|
||||
}
|
||||
},
|
||||
"calculatedTags": [
|
||||
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes' : 'no'"
|
||||
]
|
||||
},
|
||||
{
|
||||
"builtin": "playground",
|
||||
|
@ -54,8 +58,12 @@
|
|||
"source": {
|
||||
"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",
|
||||
"geoJsonZoomLevel": 14
|
||||
}
|
||||
"geoJsonZoomLevel": 14,
|
||||
"isOsmCache": true
|
||||
},
|
||||
"calculatedTags": [
|
||||
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes' : 'no'"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -66,8 +74,12 @@
|
|||
"source": {
|
||||
"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",
|
||||
"geoJsonZoomLevel": 14
|
||||
}
|
||||
"geoJsonZoomLevel": 14,
|
||||
"isOsmCache": true
|
||||
},
|
||||
"calculatedTags": [
|
||||
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes' : 'no'"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -78,8 +90,12 @@
|
|||
"source": {
|
||||
"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",
|
||||
"geoJsonZoomLevel": 14
|
||||
}
|
||||
"geoJsonZoomLevel": 14,
|
||||
"isOsmCache": true
|
||||
},
|
||||
"calculatedTags": [
|
||||
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes' : 'no'"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -89,20 +105,26 @@
|
|||
"source": {
|
||||
"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",
|
||||
"geoJsonZoomLevel": 14
|
||||
}
|
||||
"geoJsonZoomLevel": 14,
|
||||
"isOsmCache": true
|
||||
},
|
||||
"calculatedTags": [
|
||||
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes' : 'no'"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"builtin": "slow_roads",
|
||||
"override": {
|
||||
"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": {
|
||||
"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",
|
||||
"geoJsonZoomLevel": 14
|
||||
"geoJson": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson",
|
||||
"geoJsonWeb": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{layer}_{z}_{x}_{y}.geojson",
|
||||
"geoJsonZoomLevel": 14,
|
||||
"isOsmCache": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -121,7 +143,8 @@
|
|||
]
|
||||
},
|
||||
"geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{z}_{x}_{y}.geojson",
|
||||
"geoJsonZoomLevel": 14
|
||||
"geoJsonZoomLevel": 14,
|
||||
"isOsmCache": true
|
||||
},
|
||||
"title": {
|
||||
"render": "Wandeling <i>{name}</i>",
|
||||
|
@ -227,5 +250,16 @@
|
|||
"render": "Maakt deel uit van {_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
173
package-lock.json
generated
|
@ -1183,7 +1183,8 @@
|
|||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0"
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
|
@ -1744,6 +1745,21 @@
|
|||
"@turf/helpers": "^6.3.0",
|
||||
"@turf/invariant": "^6.3.0",
|
||||
"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": {
|
||||
|
@ -1882,6 +1898,21 @@
|
|||
"@turf/helpers": "^6.3.0",
|
||||
"@turf/invariant": "^6.3.0",
|
||||
"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": {
|
||||
|
@ -2588,6 +2619,21 @@
|
|||
"@turf/helpers": "^6.3.0",
|
||||
"@turf/invariant": "^6.3.0",
|
||||
"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": {
|
||||
|
@ -7770,6 +7816,59 @@
|
|||
"requires": {
|
||||
"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",
|
||||
"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": {
|
||||
"version": "3.1.22",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
|
||||
|
@ -10820,11 +10917,6 @@
|
|||
"integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==",
|
||||
"dev": true
|
||||
},
|
||||
"splaytree": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.0.tgz",
|
||||
"integrity": "sha512-gvUGR7xnOy0fLKTCxDeUZYgU/I1Tdf8M/lM1Qrf8L2TIOR5ipZjGk02uYcdv0o2x7WjVRgpm3iS2clLyuVAt0Q=="
|
||||
},
|
||||
"split": {
|
||||
"version": "0.2.10",
|
||||
"resolved": "https://registry.npmjs.org/split/-/split-0.2.10.tgz",
|
||||
|
@ -11587,59 +11679,6 @@
|
|||
"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": {
|
||||
"version": "3.0.12",
|
||||
"resolved": "https://registry.npmjs.org/turf-along/-/turf-along-3.0.12.tgz",
|
||||
|
|
|
@ -9,14 +9,14 @@
|
|||
"scripts": {
|
||||
"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/*/*",
|
||||
"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",
|
||||
"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:translations": "ts-node scripts/generateTranslations.ts",
|
||||
"generate:layouts": "ts-node scripts/generateLayouts.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:licenses": "ts-node scripts/generateLicenseInfo.ts --no-fail",
|
||||
"generate:report": "cd Docs/Tools && ./compileStats.sh && git commit . -m 'New statistics ands graphs' && git push",
|
||||
|
@ -79,8 +79,7 @@
|
|||
"sharp": "^0.27.0",
|
||||
"slick-carousel": "^1.8.1",
|
||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2",
|
||||
"tslint": "^6.1.3",
|
||||
"turf": "^3.0.14"
|
||||
"tslint": "^6.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/polyfill": "^7.10.4",
|
||||
|
|
|
@ -18,9 +18,9 @@ export default class ScriptUtils {
|
|||
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) => {
|
||||
console.log("Got response!")
|
||||
const parts : string[] = []
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function (chunk) {
|
||||
|
@ -29,17 +29,27 @@ export default class ScriptUtils {
|
|||
});
|
||||
|
||||
res.addListener('end', function () {
|
||||
continuation(parts)
|
||||
const result = parts.join("")
|
||||
try{
|
||||
resolve(JSON.parse(result))
|
||||
}catch (e){
|
||||
reject(e)
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
public static sleep(ms) {
|
||||
if(ms <= 0){
|
||||
process.stdout.write("\r \r")
|
||||
return;
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
console.debug("Sleeping for", ms)
|
||||
setTimeout(resolve, ms);
|
||||
|
||||
});
|
||||
process.stdout.write("\r Sleeping for "+(ms/1000)+"s \r")
|
||||
setTimeout(resolve, 1000);
|
||||
}).then(() => ScriptUtils.sleep(ms - 1000));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import * as OsmToGeoJson from "osmtogeojson";
|
|||
import MetaTagging from "../Logic/MetaTagging";
|
||||
import LayerConfig from "../Customizations/JSON/LayerConfig";
|
||||
import {GeoOperations} from "../Logic/GeoOperations";
|
||||
import {fail} from "assert";
|
||||
|
||||
|
||||
function createOverpassObject(theme: LayoutConfig) {
|
||||
|
@ -29,8 +30,11 @@ function createOverpassObject(theme: LayoutConfig) {
|
|||
continue;
|
||||
}
|
||||
if (layer.source.geojsonSource !== undefined) {
|
||||
// We download these anyway - we are building the cache after all!
|
||||
//continue;
|
||||
// This layer defines a geoJson-source
|
||||
// SHould it be cached?
|
||||
if (!layer.source.isOsmCacheLayer) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -49,17 +53,6 @@ function createOverpassObject(theme: LayoutConfig) {
|
|||
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 {
|
||||
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"
|
||||
}
|
||||
|
||||
/// Downloads the given feature and saves them to disk
|
||||
async function downloadRaw(targetdir: string, r: TileRange, overpass: Overpass)/* : {failed: number, skipped :number} */ {
|
||||
let downloaded = 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 + "]")
|
||||
|
||||
let gotResponse = false
|
||||
let success = false;
|
||||
ScriptUtils.DownloadJSON(url,
|
||||
chunks => {
|
||||
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")
|
||||
await ScriptUtils.DownloadJSON(url)
|
||||
.then(json => {
|
||||
console.log("Got the response - writing to ", filename)
|
||||
writeFileSync(filename, JSON.stringify(json, null, " "));
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
)
|
||||
.catch(err => {
|
||||
console.log("Could not download - probably hit the rate limit; waiting a bit")
|
||||
failed++;
|
||||
console.log("Hit the rate limit - waiting 90s")
|
||||
for (let i = 0; i < 90; i++) {
|
||||
console.log(90 - i)
|
||||
await ScriptUtils.sleep(1000)
|
||||
}
|
||||
}
|
||||
return ScriptUtils.sleep(60000).then(() => console.log("Waiting is done"))
|
||||
})
|
||||
// Cooldown
|
||||
console.debug("Cooling down 10s")
|
||||
await ScriptUtils.sleep(10000)
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
const layerIndex = theme.LayerIndex();
|
||||
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)
|
||||
console.log(" Post processing", processed, "/", r.total, 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
|
||||
|
@ -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
|
||||
const geojson = OsmToGeoJson.default(rawOsm);
|
||||
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) {
|
||||
|
||||
|
@ -149,9 +157,6 @@ async function postProcess(targetdir: string, r: TileRange, theme: LayoutConfig)
|
|||
break;
|
||||
}
|
||||
}
|
||||
if(feature["_matching_layer_id"] === undefined){
|
||||
console.log("No matching layer found for ", feature.properties.id)
|
||||
}
|
||||
}
|
||||
const featuresFreshness = geojson.features.map(feature => {
|
||||
return ({
|
||||
|
@ -161,6 +166,7 @@ async function postProcess(targetdir: string, r: TileRange, theme: LayoutConfig)
|
|||
});
|
||||
// Extract the relationship information
|
||||
const relations = ExtractRelations.BuildMembershipTable(ExtractRelations.GetRelationElements(rawOsm))
|
||||
|
||||
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"]
|
||||
}
|
||||
|
||||
|
||||
writeFileSync(geoJsonName(targetdir, x, y, r.zoomlevel), JSON.stringify(geojson, null, " "))
|
||||
const targetPath = geoJsonName(targetdir+".unfiltered", x, y, r.zoomlevel)
|
||||
// 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;
|
||||
for (let x = r.xstart; x <= r.xend; x++) {
|
||||
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) {
|
||||
const geojson = JSON.parse(file)
|
||||
geojson.features = geojson.features.filter(f => f._matching_layer_id === layer.id)
|
||||
if (geojson.features.length == 0) {
|
||||
if (!layer.source.isOsmCacheLayer) {
|
||||
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);
|
||||
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, " "))
|
||||
}
|
||||
|
||||
|
@ -243,11 +269,12 @@ async function main(args: string[]) {
|
|||
const cachingResult = await downloadRaw(targetdir, tileRange, overpass)
|
||||
failed = cachingResult.failed
|
||||
if (failed > 0) {
|
||||
ScriptUtils.sleep(30000)
|
||||
await ScriptUtils.sleep(30000)
|
||||
}
|
||||
} while (failed > 0)
|
||||
|
||||
await postProcess(targetdir, tileRange, theme)
|
||||
const extraFeatures = await downloadExtraData(theme);
|
||||
await postProcess(targetdir, tileRange, theme, extraFeatures)
|
||||
await splitPerLayer(targetdir, tileRange, theme)
|
||||
}
|
||||
|
||||
|
|
|
@ -32,8 +32,13 @@ const themeFiles: any[] = ScriptUtils.readDirRecSync("./assets/themes")
|
|||
.filter(path => path.endsWith(".json"))
|
||||
.filter(path => path.indexOf("license_info.json") < 0)
|
||||
.map(path => {
|
||||
try{
|
||||
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")
|
||||
return {
|
||||
|
|
197
test/GeoOperations.spec.ts
Normal file
197
test/GeoOperations.spec.ts
Normal 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)
|
||||
}]
|
||||
]
|
||||
)
|
||||
|
||||
}
|
||||
}
|
|
@ -20,8 +20,10 @@ import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
|
|||
import AllKnownLayers from "../Customizations/AllKnownLayers";
|
||||
import LayerConfig from "../Customizations/JSON/LayerConfig";
|
||||
|
||||
|
||||
new T("ImageAttribution Tests", [
|
||||
export default class ImageAttributionSpec extends T {
|
||||
constructor() {
|
||||
super(
|
||||
"ImageAttribution Tests", [
|
||||
[
|
||||
"Should find all the images",
|
||||
() => {
|
||||
|
@ -55,4 +57,6 @@ new T("ImageAttribution Tests", [
|
|||
]
|
||||
|
||||
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -16,9 +16,10 @@ import {SubstitutedTranslation} from "../UI/SubstitutedTranslation";
|
|||
import {Tag} from "../Logic/Tags/Tag";
|
||||
import {And} from "../Logic/Tags/And";
|
||||
import {ImageSearcher} from "../Logic/Actors/ImageSearcher";
|
||||
export default class ImageSearcherSpec extends T {
|
||||
|
||||
|
||||
new T("ImageSearcher", [
|
||||
constructor() {
|
||||
super("ImageSearcher", [
|
||||
[
|
||||
"Should find images",
|
||||
() => {
|
||||
|
@ -32,4 +33,9 @@ new T("ImageSearcher", [
|
|||
]
|
||||
|
||||
|
||||
])
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import {Utils} from "../Utils";
|
||||
|
||||
Utils.runningFromConsole = true;
|
||||
import {equal} from "assert";
|
||||
import T from "./TestHelper";
|
||||
import {FromJSON} from "../Customizations/JSON/FromJSON";
|
||||
|
@ -15,10 +13,13 @@ 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";
|
||||
|
||||
Utils.runningFromConsole = true;
|
||||
|
||||
new T("Tags", [
|
||||
export default class TagSpec extends T{
|
||||
|
||||
constructor() {
|
||||
super("Tags", [
|
||||
["Tag replacement works in translation", () => {
|
||||
const tr = new Translation({
|
||||
"en": "Test {key} abc"
|
||||
|
@ -426,3 +427,6 @@ new T("Tags", [
|
|||
equal(true, tagRendering.IsKnown({bottle: "yes"}))
|
||||
equal(false, tagRendering.IsKnown({}))
|
||||
}]]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,8 +7,9 @@ import {UIEventSource} from "../Logic/UIEventSource";
|
|||
import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig";
|
||||
import EditableTagRendering from "../UI/Popup/EditableTagRendering";
|
||||
|
||||
|
||||
new T("TagQuestionElement",
|
||||
export default class TagQuestionSpec extends T {
|
||||
constructor() {
|
||||
super("TagQuestionElement",
|
||||
[
|
||||
["Freeform has textfield", () => {
|
||||
const tags = new UIEventSource({
|
||||
|
@ -44,8 +45,10 @@ new T("TagQuestionElement",
|
|||
type: "string"
|
||||
},
|
||||
mappings: [
|
||||
{"if": "noname=yes",
|
||||
"then": "This bookcase has no name"}
|
||||
{
|
||||
"if": "noname=yes",
|
||||
"then": "This bookcase has no name"
|
||||
}
|
||||
]
|
||||
}, undefined, "Testing tag"
|
||||
);
|
||||
|
@ -55,5 +58,6 @@ new T("TagQuestionElement",
|
|||
T.assertContains("This bookcase has no name", html);
|
||||
T.assertContains("<input type='text'", html);
|
||||
}]
|
||||
]
|
||||
);
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
27
test/TestAll.ts
Normal file
27
test/TestAll.ts
Normal 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"
|
||||
}
|
||||
}
|
|
@ -1,21 +1,21 @@
|
|||
|
||||
export default class T {
|
||||
|
||||
public readonly failures = []
|
||||
|
||||
constructor(testsuite: string, tests: [string, () => void][]) {
|
||||
let failures : string []= [];
|
||||
for (const [name, test] of tests) {
|
||||
try {
|
||||
test();
|
||||
} catch (e) {
|
||||
failures.push(name);
|
||||
this.failures.push(name);
|
||||
console.warn("Failed test: ", name, "because", e);
|
||||
}
|
||||
}
|
||||
if (failures.length == 0) {
|
||||
if (this.failures.length == 0) {
|
||||
console.log(`All tests of ${testsuite} done!`)
|
||||
} else {
|
||||
console.warn(failures.length, `tests of ${testsuite} failed :(`)
|
||||
console.log("Failed tests: ", failures.join(","))
|
||||
console.warn(this.failures.length, `tests of ${testsuite} failed :(`)
|
||||
console.log("Failed tests: ", this.failures.join(","))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,4 +30,9 @@ export default class T {
|
|||
throw "Expected true, but got false: " + msg
|
||||
}
|
||||
}
|
||||
static isFalse(b: boolean, msg: string) {
|
||||
if (b) {
|
||||
throw "Expected false, but got true: " + msg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,9 @@ import LayoutConfig from "../Customizations/JSON/LayoutConfig";
|
|||
import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson";
|
||||
import * as assert from "assert";
|
||||
|
||||
|
||||
new T("Theme tests",
|
||||
export default class ThemeSpec extends T{
|
||||
constructor() {
|
||||
super("Theme tests",
|
||||
[
|
||||
["Nested overrides work", () => {
|
||||
|
||||
|
@ -44,5 +45,6 @@ new T("Theme tests",
|
|||
|
||||
|
||||
}]
|
||||
]
|
||||
);
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,45 @@
|
|||
import T from "./TestHelper";
|
||||
import {Utils} from "../Utils";
|
||||
import {equal} from "assert";
|
||||
import {existsSync, mkdirSync, readFileSync, writeFile, writeFileSync} from "fs";
|
||||
import LZString from "lz-string";
|
||||
new T("Utils",[
|
||||
|
||||
export default class UtilsSpec extends T {
|
||||
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);
|
||||
|
@ -16,8 +52,7 @@ new T("Utils",[
|
|||
|
||||
}],
|
||||
["Minify-json of the bookcases", () => {
|
||||
let str = readFileSync("/home/pietervdvn/git/MapComplete/assets/layers/public_bookcases/public_bookcases.json", "UTF8")
|
||||
str = JSON.stringify(JSON.parse(str), null, 0)
|
||||
const str = JSON.stringify(UtilsSpec.example, null, 0)
|
||||
const minified = Utils.MinifyJSON(str);
|
||||
console.log("Minified version has ", minified.length, "chars")
|
||||
const restored = Utils.UnMinify(minified)
|
||||
|
@ -26,8 +61,7 @@ new T("Utils",[
|
|||
|
||||
}],
|
||||
["Minify-json with LZ-string of the bookcases", () => {
|
||||
let str = readFileSync("/home/pietervdvn/git/MapComplete/assets/layers/public_bookcases/public_bookcases.json", "UTF8")
|
||||
str = JSON.stringify(JSON.parse(str), null, 0)
|
||||
const str = JSON.stringify(UtilsSpec.example, null, 0)
|
||||
const minified = LZString.compressToBase64(Utils.MinifyJSON(str));
|
||||
console.log("Minified version has ", minified.length, "chars")
|
||||
const restored = Utils.UnMinify(LZString.decompressFromBase64(minified))
|
||||
|
@ -36,8 +70,7 @@ new T("Utils",[
|
|||
|
||||
}],
|
||||
["Minify-json with only LZ-string of the bookcases", () => {
|
||||
let str = readFileSync("/home/pietervdvn/git/MapComplete/assets/layers/public_bookcases/public_bookcases.json", "UTF8")
|
||||
str = JSON.stringify(JSON.parse(str), null, 0)
|
||||
const str = JSON.stringify(UtilsSpec.example, null, 0)
|
||||
const minified = LZString.compressToBase64(str);
|
||||
console.log("Minified version has ", minified.length, "chars")
|
||||
const restored = LZString.decompressFromBase64(minified)
|
||||
|
@ -45,6 +78,7 @@ new T("Utils",[
|
|||
equal(str, restored)
|
||||
|
||||
}]
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
])
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue