diff --git a/InitUiElements.ts b/InitUiElements.ts
index e3e61ce2e..353192dc3 100644
--- a/InitUiElements.ts
+++ b/InitUiElements.ts
@@ -1,7 +1,6 @@
import {FixedUiElement} from "./UI/Base/FixedUiElement";
import Toggle from "./UI/Input/Toggle";
import State from "./State";
-import LoadFromOverpass from "./Logic/Actors/OverpassFeatureSource";
import {UIEventSource} from "./Logic/UIEventSource";
import {QueryParameters} from "./Logic/Web/QueryParameters";
import StrayClickHandler from "./Logic/Actors/StrayClickHandler";
@@ -18,17 +17,15 @@ import * as L from "leaflet";
import Img from "./UI/Base/Img";
import UserDetails from "./Logic/Osm/OsmConnection";
import Attribution from "./UI/BigComponents/Attribution";
-import LayerResetter from "./Logic/Actors/LayerResetter";
+import BackgroundLayerResetter from "./Logic/Actors/BackgroundLayerResetter";
import FullWelcomePaneWithTabs from "./UI/BigComponents/FullWelcomePaneWithTabs";
-import ShowDataLayer from "./UI/ShowDataLayer";
+import ShowDataLayer from "./UI/ShowDataLayer/ShowDataLayer";
import Hash from "./Logic/Web/Hash";
import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline";
import ScrollableFullScreen from "./UI/Base/ScrollableFullScreen";
import Translations from "./UI/i18n/Translations";
import MapControlButton from "./UI/MapControlButton";
-import SelectedFeatureHandler from "./Logic/Actors/SelectedFeatureHandler";
import LZString from "lz-string";
-import FeatureSource from "./Logic/FeatureSource/FeatureSource";
import AllKnownLayers from "./Customizations/AllKnownLayers";
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
import {TagsFilter} from "./Logic/Tags/TagsFilter";
@@ -38,7 +35,6 @@ import {LayoutConfigJson} from "./Models/ThemeConfig/Json/LayoutConfigJson";
import LayoutConfig from "./Models/ThemeConfig/LayoutConfig";
import LayerConfig from "./Models/ThemeConfig/LayerConfig";
import Minimap from "./UI/Base/Minimap";
-import Constants from "./Models/Constants";
export class InitUiElements {
static InitAll(
@@ -130,10 +126,9 @@ export class InitUiElements {
}
}
if (somethingChanged) {
- console.log("layoutToUse.layers:", layoutToUse.layers);
State.state.layoutToUse.data.layers = Array.from(neededLayers);
State.state.layoutToUse.ping();
- State.state.layerUpdater?.ForceRefresh();
+ State.state.featurePipeline?.ForceRefresh();
}
}
@@ -320,7 +315,7 @@ export class InitUiElements {
(layer) => layer.id
);
- new LayerResetter(
+ new BackgroundLayerResetter(
State.state.backgroundLayer,
State.state.locationControl,
State.state.availableBackgroundLayers,
@@ -333,13 +328,14 @@ export class InitUiElements {
State.state.locationControl,
State.state.osmConnection.userDetails,
State.state.layoutToUse,
- State.state.leafletMap
+ State.state.currentBounds
);
- new Minimap({
+ Minimap.createMiniMap({
background: State.state.backgroundLayer,
location: State.state.locationControl,
leafletMap: State.state.leafletMap,
+ bounds: State.state.currentBounds,
attribution: attr,
lastClickLocation: State.state.LastClickLocation
}).SetClass("w-full h-full")
@@ -371,7 +367,7 @@ export class InitUiElements {
}
}
- private static InitLayers(): FeatureSource {
+ private static InitLayers(): void {
const state = State.state;
state.filteredLayers = state.layoutToUse.map((layoutToUse) => {
const flayers = [];
@@ -396,51 +392,35 @@ export class InitUiElements {
return flayers;
});
- const updater = new LoadFromOverpass(
- state.locationControl,
- state.layoutToUse,
- state.leafletMap,
- state.overpassUrl,
- state.overpassTimeout,
- Constants.useOsmApiAt
- );
- State.state.layerUpdater = updater;
-
- const source = new FeaturePipeline(
- state.filteredLayers,
- State.state.changes,
- updater,
- state.osmApiFeatureSource,
- state.layoutToUse,
- state.locationControl,
- state.selectedElement
+ State.state.featurePipeline = new FeaturePipeline(
+ source => {
+ new ShowDataLayer(
+ {
+ features: source,
+ leafletMap: State.state.leafletMap,
+ layerToShow: source.layer.layerDef
+ }
+ );
+ }, state
);
- State.state.featurePipeline = source;
- new ShowDataLayer(
- source.features,
- State.state.leafletMap,
- State.state.layoutToUse
- );
-
- const selectedFeatureHandler = new SelectedFeatureHandler(
- Hash.hash,
- State.state.selectedElement,
- source,
- State.state.osmApiFeatureSource
- );
- selectedFeatureHandler.zoomToSelectedFeature(
- State.state.locationControl
- );
- return source;
+ /* const selectedFeatureHandler = new SelectedFeatureHandler(
+ Hash.hash,
+ State.state.selectedElement,
+ source,
+ State.state.osmApiFeatureSource
+ );
+ selectedFeatureHandler.zoomToSelectedFeature(
+ State.state.locationControl
+ );*/
}
private static setupAllLayerElements() {
// ------------- Setup the layers -------------------------------
- const source = InitUiElements.InitLayers();
+ InitUiElements.InitLayers();
- new LeftControls(source).AttachTo("bottom-left");
+ new LeftControls(State.state).AttachTo("bottom-left");
new RightControls().AttachTo("bottom-right");
// ------------------ Setup various other UI elements ------------
diff --git a/Logic/Actors/BackgroundLayerResetter.ts b/Logic/Actors/BackgroundLayerResetter.ts
index 1a2175dfc..96c43bda3 100644
--- a/Logic/Actors/BackgroundLayerResetter.ts
+++ b/Logic/Actors/BackgroundLayerResetter.ts
@@ -6,7 +6,7 @@ import Loc from "../../Models/Loc";
/**
* Sets the current background layer to a layer that is actually available
*/
-export default class LayerResetter {
+export default class BackgroundLayerResetter {
constructor(currentBackgroundLayer: UIEventSource,
location: UIEventSource,
diff --git a/Logic/Actors/OverpassFeatureSource.ts b/Logic/Actors/OverpassFeatureSource.ts
index 4d4110935..e788c7616 100644
--- a/Logic/Actors/OverpassFeatureSource.ts
+++ b/Logic/Actors/OverpassFeatureSource.ts
@@ -3,14 +3,15 @@ import Loc from "../../Models/Loc";
import {Or} from "../Tags/Or";
import {Overpass} from "../Osm/Overpass";
import Bounds from "../../Models/Bounds";
-import FeatureSource from "../FeatureSource/FeatureSource";
+import FeatureSource, {FeatureSourceState} from "../FeatureSource/FeatureSource";
import {Utils} from "../../Utils";
import {TagsFilter} from "../Tags/TagsFilter";
import SimpleMetaTagger from "../SimpleMetaTagger";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
+import RelationsTracker from "../Osm/RelationsTracker";
-export default class OverpassFeatureSource implements FeatureSource {
+export default class OverpassFeatureSource implements FeatureSource, FeatureSourceState {
public readonly name = "OverpassFeatureSource"
@@ -24,6 +25,9 @@ export default class OverpassFeatureSource implements FeatureSource {
public readonly runningQuery: UIEventSource = new UIEventSource(false);
public readonly timeout: UIEventSource = new UIEventSource(0);
+ public readonly relationsTracker: RelationsTracker;
+
+
private readonly retries: UIEventSource = new UIEventSource(0);
/**
* The previous bounds for which the query has been run at the given zoom level
@@ -33,56 +37,61 @@ export default class OverpassFeatureSource implements FeatureSource {
* we start checking the bounds at the first zoom level the layer might operate. If in bounds - no reload needed, otherwise we continue walking down
*/
private readonly _previousBounds: Map = new Map();
- private readonly _location: UIEventSource;
- private readonly _layoutToUse: UIEventSource;
- private readonly _leafletMap: UIEventSource;
- private readonly _interpreterUrl: UIEventSource;
- private readonly _timeout: UIEventSource;
+ private readonly state: {
+ readonly locationControl: UIEventSource,
+ readonly layoutToUse: UIEventSource,
+ readonly leafletMap: any,
+ readonly overpassUrl: UIEventSource;
+ readonly overpassTimeout: UIEventSource;
+ }
/**
* The most important layer should go first, as that one gets first pick for the questions
*/
constructor(
- location: UIEventSource,
- layoutToUse: UIEventSource,
- leafletMap: UIEventSource,
- interpreterUrl: UIEventSource,
- timeout: UIEventSource,
- maxZoom = undefined) {
- this._location = location;
- this._layoutToUse = layoutToUse;
- this._leafletMap = leafletMap;
- this._interpreterUrl = interpreterUrl;
- this._timeout = timeout;
+ state: {
+ readonly locationControl: UIEventSource,
+ readonly layoutToUse: UIEventSource,
+ readonly leafletMap: any,
+ readonly overpassUrl: UIEventSource;
+ readonly overpassTimeout: UIEventSource;
+ readonly overpassMaxZoom: UIEventSource
+ }) {
+
+
+ this.state = state
+ this.relationsTracker = new RelationsTracker()
+ const location = state.locationControl
const self = this;
this.sufficientlyZoomed = location.map(location => {
if (location?.zoom === undefined) {
return false;
}
- let minzoom = Math.min(...layoutToUse.data.layers.map(layer => layer.minzoom ?? 18));
- if(location.zoom < minzoom){
+ let minzoom = Math.min(...state.layoutToUse.data.layers.map(layer => layer.minzoom ?? 18));
+ if (location.zoom < minzoom) {
return false;
}
- if(maxZoom !== undefined && location.zoom > maxZoom){
+ const maxZoom = state.overpassMaxZoom.data
+ if (maxZoom !== undefined && location.zoom > maxZoom) {
return false;
}
-
+
return true;
- }, [layoutToUse]
+ }, [state.layoutToUse]
);
for (let i = 0; i < 25; i++) {
// This update removes all data on all layers -> erase the map on lower levels too
this._previousBounds.set(i, []);
}
- layoutToUse.addCallback(() => {
+ state.layoutToUse.addCallback(() => {
self.update()
});
location.addCallback(() => {
self.update()
});
- leafletMap.addCallbackAndRunD(_ => {
+ state.leafletMap.addCallbackAndRunD(_ => {
self.update();
})
}
@@ -97,11 +106,11 @@ export default class OverpassFeatureSource implements FeatureSource {
private GetFilter(): Overpass {
let filters: TagsFilter[] = [];
let extraScripts: string[] = [];
- for (const layer of this._layoutToUse.data.layers) {
+ for (const layer of this.state.layoutToUse.data.layers) {
if (typeof (layer) === "string") {
throw "A layer was not expanded!"
}
- if (this._location.data.zoom < layer.minzoom) {
+ if (this.state.locationControl.data.zoom < layer.minzoom) {
continue;
}
if (layer.doNotDownload) {
@@ -141,7 +150,7 @@ export default class OverpassFeatureSource implements FeatureSource {
if (filters.length + extraScripts.length === 0) {
return undefined;
}
- return new Overpass(new Or(filters), extraScripts, this._interpreterUrl, this._timeout);
+ return new Overpass(new Or(filters), extraScripts, this.state.overpassUrl, this.state.overpassTimeout, this.relationsTracker);
}
private update(): void {
@@ -155,21 +164,22 @@ export default class OverpassFeatureSource implements FeatureSource {
return;
}
- const bounds = this._leafletMap.data?.getBounds()?.pad( this._layoutToUse.data.widenFactor);
+ const bounds = this.state.leafletMap.data?.getBounds()?.pad(this.state.layoutToUse.data.widenFactor);
if (bounds === undefined) {
return;
}
- const n = Math.min(90, bounds.getNorth() );
- const e = Math.min(180, bounds.getEast() );
+ const n = Math.min(90, bounds.getNorth());
+ const e = Math.min(180, bounds.getEast());
const s = Math.max(-90, bounds.getSouth());
const w = Math.max(-180, bounds.getWest());
const queryBounds = {north: n, east: e, south: s, west: w};
- const z = Math.floor(this._location.data.zoom ?? 0);
+ const z = Math.floor(this.state.locationControl.data.zoom ?? 0);
const self = this;
const overpass = this.GetFilter();
+
if (overpass === undefined) {
return;
}
@@ -181,14 +191,18 @@ export default class OverpassFeatureSource implements FeatureSource {
const features = data.features.map(f => ({feature: f, freshness: date}));
SimpleMetaTagger.objectMetaInfo.addMetaTags(features)
- self.features.setData(features);
+ try{
+ self.features.setData(features);
+ }catch(e){
+ console.error("Got the overpass response, but could not process it: ", e, e.stack)
+ }
self.runningQuery.setData(false);
},
function (reason) {
self.retries.data++;
self.ForceRefresh();
self.timeout.setData(self.retries.data * 5);
- console.log(`QUERY FAILED (retrying in ${5 * self.retries.data} sec) due to ${reason}`);
+ console.error(`QUERY FAILED (retrying in ${5 * self.retries.data} sec) due to ${reason}`);
self.retries.ping();
self.runningQuery.setData(false);
@@ -222,7 +236,7 @@ export default class OverpassFeatureSource implements FeatureSource {
return false;
}
- const b = this._leafletMap.data.getBounds();
+ const b = this.state.leafletMap.data.getBounds();
return b.getSouth() >= bounds.south &&
b.getNorth() <= bounds.north &&
b.getEast() <= bounds.east &&
diff --git a/Logic/Actors/SelectedFeatureHandler.ts b/Logic/Actors/SelectedFeatureHandler.ts
index 5a63d5cb2..07ff7f4a7 100644
--- a/Logic/Actors/SelectedFeatureHandler.ts
+++ b/Logic/Actors/SelectedFeatureHandler.ts
@@ -3,7 +3,7 @@ import FeatureSource from "../FeatureSource/FeatureSource";
import {OsmObject} from "../Osm/OsmObject";
import Loc from "../../Models/Loc";
import FeaturePipeline from "../FeatureSource/FeaturePipeline";
-import OsmApiFeatureSource from "../FeatureSource/OsmApiFeatureSource";
+import OsmApiFeatureSource from "../FeatureSource/Sources/OsmApiFeatureSource";
/**
* Makes sure the hash shows the selected element and vice-versa.
diff --git a/Logic/ContributorCount.ts b/Logic/ContributorCount.ts
index 9c954bdfb..6a4c2da25 100644
--- a/Logic/ContributorCount.ts
+++ b/Logic/ContributorCount.ts
@@ -1,21 +1,49 @@
/// Given a feature source, calculates a list of OSM-contributors who mapped the latest versions
import FeatureSource from "./FeatureSource/FeatureSource";
import {UIEventSource} from "./UIEventSource";
+import FeaturePipeline from "./FeatureSource/FeaturePipeline";
+import Loc from "../Models/Loc";
+import State from "../State";
+import {BBox} from "./GeoOperations";
export default class ContributorCount {
- public readonly Contributors: UIEventSource
Did you notice an issue? Do you have a feature request? Want to help translate? Head over to the source code or issue tracker.
Want to see your progress? Follow the edit count on OsmCha.
",
+ "aboutMapcomplete": "About MapComplete
With MapComplete you can enrich OpenStreetMap with information on a single theme. Answer a few questions, and within minutes your contributions will be available around the globe! The theme maintainer defines elements, questions and languages for the theme.
Find out more
MapComplete always offers the next step to learn more about OpenStreetMap.
- When embedded in a website, the iframe links to a full-screen MapComplete
- The full-screen version offers information about OpenStreetMap
- Viewing works without login, but editing requires an OSM login.
- If you are not logged in, you are asked to log in
- Once you answered a single question, you can add new points to the map
- After a while, actual OSM-tags are shown, later linking to the wiki
Did you notice an issue? Do you have a feature request? Want to help translate? Head over to the source code or issue tracker.
Want to see your progress? Follow the edit count on OsmCha.
",
"backgroundMap": "Background map",
"openTheMap": "Open the map",
"loginOnlyNeededToEdit": "if you want to edit the map",
diff --git a/langs/layers/en.json b/langs/layers/en.json
index d7910039a..9149f1966 100644
--- a/langs/layers/en.json
+++ b/langs/layers/en.json
@@ -3021,6 +3021,29 @@
}
},
"toilet": {
+ "filter": {
+ "0": {
+ "options": {
+ "0": {
+ "question": "Wheelchair accessible"
+ }
+ }
+ },
+ "1": {
+ "options": {
+ "0": {
+ "question": "Has a changing table"
+ }
+ }
+ },
+ "2": {
+ "options": {
+ "0": {
+ "question": "Free to use"
+ }
+ }
+ }
+ },
"name": "Toilets",
"presets": {
"0": {
diff --git a/langs/nl.json b/langs/nl.json
index d774fea79..3b24c50cd 100644
--- a/langs/nl.json
+++ b/langs/nl.json
@@ -192,7 +192,7 @@
"getStartedNewAccount": " of maak een nieuwe account aan",
"noTagsSelected": "Geen tags geselecteerd",
"customThemeIntro": "Onofficiële thema's
De onderstaande thema's heb je eerder bezocht en zijn gemaakt door andere OpenStreetMappers.",
- "aboutMapcomplete": "Over MapComplete
Met MapComplete kun je OpenStreetMap verrijken met informatie over een bepaald thema. Beantwoord enkele vragen, en binnen een paar minuten is jouw bijdrage wereldwijd beschikbaar! De maker van het thema bepaalt de elementen, vragen en taalversies voor het thema.
Ontdek meer
MapComplete biedt altijd de volgende stap naar meer OpenStreetMap:
- Indien ingebed in een website linkt het iframe naar de volledige MapComplete
- De volledige versie heeft uitleg over OpenStreetMap
- Bekijken kan altijd, maar wijzigen vereist een OSM-account
- Als je niet aangemeld bent, wordt je gevraagd dit te doen
- Als je minstens één vraag hebt beantwoord, kan je ook elementen toevoegen
- Heb je genoeg changesets, dan verschijnen de OSM-tags, nog later links naar de wiki
Merk je een bug of wil je een extra feature? Wil je helpen vertalen? Bezoek dan de broncode en issue tracker.
Wil je je vorderingen zien? Volg de edits op OsmCha.",
+ "aboutMapcomplete": "Over MapComplete
Met MapComplete kun je OpenStreetMap verrijken met informatie over een bepaald thema. Beantwoord enkele vragen, en binnen een paar minuten is jouw bijdrage wereldwijd beschikbaar! De maker van het thema bepaalt de elementen, vragen en taalversies voor het thema.
Ontdek meer
MapComplete biedt altijd de volgende stap naar meer OpenStreetMap:
- Indien ingebed in een website linkt het iframe naar de volledige MapComplete
- De volledige versie heeft uitleg over OpenStreetMap
- Bekijken kan altijd, maar wijzigen vereist een OSM-account
- Als je niet aangemeld bent, wordt je gevraagd dit te doen
- Als je minstens één vraag hebt beantwoord, kan je ook elementen toevoegen
- Heb je genoeg changesets, dan verschijnen de OSM-tags, nog later links naar de wiki
Merk je een bug of wil je een extra feature? Wil je helpen vertalen? Bezoek dan de broncode en issue tracker.
Wil je je vorderingen zien? Volg de edits op OsmCha.",
"backgroundMap": "Achtergrondkaart",
"layerSelection": {
"zoomInToSeeThisLayer": "Vergroot de kaart om deze laag te zien",
diff --git a/langs/themes/en.json b/langs/themes/en.json
index 94eaf954b..e96960d80 100644
--- a/langs/themes/en.json
+++ b/langs/themes/en.json
@@ -1351,6 +1351,11 @@
"title": {
"render": "Known address"
}
+ },
+ "2": {
+ "title": {
+ "render": "{name}"
+ }
}
},
"shortDescription": "Help to build an open dataset of UK addresses",
diff --git a/package.json b/package.json
index 8baeb62fd..31f2aaa7d 100644
--- a/package.json
+++ b/package.json
@@ -20,10 +20,10 @@
"reset:translations": "ts-node scripts/generateTranslations.ts --ignore-weblate",
"generate:layouts": "ts-node scripts/generateLayouts.ts",
"generate:docs": "ts-node scripts/generateDocs.ts && ts-node scripts/generateTaginfoProjectFiles.ts",
- "generate:cache:speelplekken:mini": "npm run generate:layeroverview && ts-node scripts/generateCache.ts speelplekken 14 ../pietervdvn.github.io/speelplekken_cache/ 51.181710380278176 4.423413276672363 51.193007664772495 4.444141387939452",
+ "generate:cache:speelplekken:mini": "ts-node scripts/generateCache.ts speelplekken 14 ../pietervdvn.github.io/speelplekken_cache_mini/ 51.181710380278176 4.423413276672363 51.193007664772495 4.444141387939452",
"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:cache:natuurpunt": "npm run generate:layeroverview && ts-node scripts/generateCache.ts natuurpunt 12 ../pietervdvn.github.io/natuurpunt_cache/ 50.40 2.1 51.54 6.4 --generate-point-overview nature_reserve,visitor_information_centre",
- "generate:layeroverview": "npm run generate:licenses && echo {\\\"layers\\\":[], \\\"themes\\\":[]} > ./assets/generated/known_layers_and_themes.json && ts-node scripts/generateLayerOverview.ts --no-fail",
+ "generate:layeroverview": "echo {\\\"layers\\\":[], \\\"themes\\\":[]} > ./assets/generated/known_layers_and_themes.json && ts-node scripts/generateLayerOverview.ts --no-fail",
"generate:licenses": "ts-node scripts/generateLicenseInfo.ts --no-fail",
"query:licenses": "ts-node scripts/generateLicenseInfo.ts --query",
"generate:report": "cd Docs/Tools && ./compileStats.sh && git commit . -m 'New statistics ands graphs' && git push",
diff --git a/scripts/ScriptUtils.ts b/scripts/ScriptUtils.ts
index 702c40f4d..d150215e7 100644
--- a/scripts/ScriptUtils.ts
+++ b/scripts/ScriptUtils.ts
@@ -7,7 +7,6 @@ import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
Utils.runningFromConsole = true
-
export default class ScriptUtils {
diff --git a/scripts/generateCache.ts b/scripts/generateCache.ts
index c043bc224..c894d5442 100644
--- a/scripts/generateCache.ts
+++ b/scripts/generateCache.ts
@@ -2,27 +2,32 @@
* Generates a collection of geojson files based on an overpass query for a given theme
*/
import {Utils} from "../Utils";
+
Utils.runningFromConsole = true
+
import {Overpass} from "../Logic/Osm/Overpass";
-import * as fs from "fs";
import {existsSync, readFileSync, writeFileSync} from "fs";
import {TagsFilter} from "../Logic/Tags/TagsFilter";
import {Or} from "../Logic/Tags/Or";
import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
-import ExtractRelations from "../Logic/Osm/ExtractRelations";
+import RelationsTracker from "../Logic/Osm/RelationsTracker";
import * as OsmToGeoJson from "osmtogeojson";
import MetaTagging from "../Logic/MetaTagging";
-import {GeoOperations} from "../Logic/GeoOperations";
import {UIEventSource} from "../Logic/UIEventSource";
import {TileRange} from "../Models/TileRange";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
-import LayerConfig from "../Models/ThemeConfig/LayerConfig";
import ScriptUtils from "./ScriptUtils";
+import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter";
+import FilteredLayer from "../Models/FilteredLayer";
+import FeatureSource, {FeatureSourceForLayer} from "../Logic/FeatureSource/FeatureSource";
+import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource";
+import TiledFeatureSource from "../Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource";
+
ScriptUtils.fixUtils()
-function createOverpassObject(theme: LayoutConfig) {
+function createOverpassObject(theme: LayoutConfig, relationTracker: RelationsTracker) {
let filters: TagsFilter[] = [];
let extraScripts: string[] = [];
for (const layer of theme.layers) {
@@ -54,7 +59,7 @@ function createOverpassObject(theme: LayoutConfig) {
throw "Nothing to download! The theme doesn't declare anything to download"
}
return new Overpass(new Or(filters), extraScripts, new UIEventSource("https://overpass.kumi.systems/api/interpreter"), //https://overpass-api.de/api/interpreter"),
- new UIEventSource(60));
+ new UIEventSource(60), relationTracker);
}
function rawJsonName(targetDir: string, x: number, y: number, z: number): string {
@@ -75,7 +80,7 @@ async function downloadRaw(targetdir: string, r: TileRange, overpass: Overpass)/
downloaded++;
const filename = rawJsonName(targetdir, x, y, r.zoomlevel)
if (existsSync(filename)) {
- console.log("Already exists: ", filename)
+ console.log("Already exists (not downloading again): ", filename)
skipped++
continue;
}
@@ -145,14 +150,16 @@ async function downloadExtraData(theme: LayoutConfig)/* : any[] */ {
return allFeatures;
}
-function postProcess(targetdir: string, r: TileRange, theme: LayoutConfig, extraFeatures: any[]) {
+
+function loadAllTiles(targetdir: string, r: TileRange, theme: LayoutConfig, extraFeatures: any[]): FeatureSource {
+
+ let allFeatures = [...extraFeatures]
let processed = 0;
- const layerIndex = theme.LayerIndex();
for (let x = r.xstart; x <= r.xend; x++) {
for (let y = r.ystart; y <= r.yend; y++) {
processed++;
const filename = rawJsonName(targetdir, x, y, r.zoomlevel)
- ScriptUtils.erasableLog(" Post processing", processed, "/", r.total, filename)
+ console.log(" Loading and processing", processed, "/", r.total, filename)
if (!existsSync(filename)) {
console.error("Not found - and not downloaded. Run this script again!: " + filename)
continue;
@@ -163,152 +170,97 @@ function postProcess(targetdir: string, r: TileRange, theme: LayoutConfig, extra
// 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) {
-
- for (const layer of theme.layers) {
- if (layer.source.osmTags.matchesProperties(feature.properties)) {
- feature["_matching_layer_id"] = layer.id;
- break;
- }
- }
- }
- const featuresFreshness = geojson.features.map(feature => {
- return ({
- freshness: osmTime,
- feature: feature
- });
- });
- // Extract the relationship information
- const relations = ExtractRelations.BuildMembershipTable(ExtractRelations.GetRelationElements(rawOsm))
-
- MetaTagging.addMetatags(featuresFreshness, new UIEventSource<{ feature: any; freshness: Date }[]>(featuresFreshness), relations, theme.layers, false);
-
-
- for (const feature of geojson.features) {
- const layer = layerIndex.get(feature["_matching_layer_id"])
- if (layer === undefined) {
- // Probably some extra, unneeded data, e.g. a point of a way
- continue
- }
-
- if (layer.wayHandling == LayerConfig.WAYHANDLING_CENTER_ONLY) {
-
- const centerpoint = GeoOperations.centerpointCoordinates(feature)
-
- feature.geometry.type = "Point"
- feature.geometry["coordinates"] = centerpoint;
-
- }
- }
- for (const feature of geojson.features) {
- // Some cleanup
- delete feature["bbox"]
- }
-
- const targetPath = geoJsonName(targetdir + ".unfiltered", x, y, r.zoomlevel)
- // This is the geojson file containing all features
- writeFileSync(targetPath, JSON.stringify(geojson, null, " "))
+ allFeatures.push(...geojson.features)
}
}
+ return new StaticFeatureSource(allFeatures)
}
-function splitPerLayer(targetdir: string, r: TileRange, theme: LayoutConfig) {
- const z = r.zoomlevel;
- const generated = {} // layer --> x --> y[]
- for (let x = r.xstart; x <= r.xend; x++) {
- for (let y = r.ystart; y <= r.yend; y++) {
- const file = readFileSync(geoJsonName(targetdir + ".unfiltered", x, y, z), "UTF8")
+/**
+ * Load all the tiles into memory from disk
+ */
+function postProcess(allFeatures: FeatureSource, theme: LayoutConfig, relationsTracker: RelationsTracker, targetdir: string) {
- for (const layer of theme.layers) {
- 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
- return isShown !== "no";
- })
- const new_path = geoJsonName(targetdir + "_" + layer.id, x, y, z);
- ScriptUtils.erasableLog(new_path, " has ", geojson.features.length, " features after filtering (dropped ", oldLength - geojson.features.length, ")")
- if (geojson.features.length == 0) {
- continue;
+ function handleLayer(source: FeatureSourceForLayer) {
+ const layer = source.layer.layerDef;
+ const layerId = layer.id
+ if (layer.source.isOsmCacheLayer !== true) {
+ return;
+ }
+ console.log("Handling layer ", layerId, "which has", source.features.data.length, "features")
+ if (source.features.data.length === 0) {
+ return;
+ }
+ MetaTagging.addMetatags(source.features.data,
+ {
+ memberships: relationsTracker,
+ getFeaturesWithin: _ => {
+ return [allFeatures.features.data.map(f => f.feature)]
}
- writeFileSync(new_path, JSON.stringify(geojson, null, " "))
+ },
+ layer,
+ false);
- if (generated[layer.id] === undefined) {
- generated[layer.id] = {}
+ const createdTiles = []
+ // At this point, we have all the features of the entire area.
+ // However, we want to export them per tile of a fixed size, so we use a dynamicTileSOurce to split it up
+ TiledFeatureSource.createHierarchy(source, {
+ minZoomLevel: 14,
+ maxZoomLevel: 14,
+ maxFeatureCount: undefined,
+ registerTile: tile => {
+ if (tile.z < 12) {
+ return;
}
- if (generated[layer.id][x] === undefined) {
- generated[layer.id][x] = []
+ if (tile.features.data.length === 0) {
+ return
}
- generated[layer.id][x].push(y)
-
+ for (const feature of tile.features.data) {
+ // Some cleanup
+ delete feature.feature["bbox"]
+ }
+ // Lets save this tile!
+ const [z, x, y] = Utils.tile_from_index(tile.tileIndex)
+ console.log("Writing tile ", z, x, y, layerId)
+ const targetPath = geoJsonName(targetdir + "_" + layerId, x, y, z)
+ createdTiles.push(tile.tileIndex)
+ // This is the geojson file containing all features for this tile
+ writeFileSync(targetPath, JSON.stringify({
+ type: "FeatureCollection",
+ features: tile.features.data.map(f => f.feature)
+ }, null, " "))
}
- }
- }
-
- for (const layer of theme.layers) {
- const id = layer.id
- const loaded = generated[id]
- if (loaded === undefined) {
- console.log("No features loaded for layer ", id)
- continue;
- }
- writeFileSync(targetdir + "_" + id + "_overview.json", JSON.stringify(loaded))
+ })
+
+ // All the tiles are written at this point
+ // Only thing left to do is to create the index
+ const path = targetdir + "_" + layerId + "_overview.json"
+ const perX = {}
+ createdTiles.map(i => Utils.tile_from_index(i)).forEach(([z, x, y]) => {
+ const key = "" + x
+ if (perX[key] === undefined) {
+ perX[key] = []
+ }
+ perX[key].push(y)
+ })
+ writeFileSync(path, JSON.stringify(perX))
+
+
}
+ new PerLayerFeatureSourceSplitter(
+ new UIEventSource(theme.layers.map(l => ({
+ layerDef: l,
+ isDisplayed: new UIEventSource(true),
+ appliedFilters: new UIEventSource(undefined)
+ }))),
+ handleLayer,
+ allFeatures
+ )
}
-async function createOverview(targetdir: string, r: TileRange, z: number, layername: string) {
- const allFeatures = []
- for (let x = r.xstart; x <= r.xend; x++) {
- for (let y = r.ystart; y <= r.yend; y++) {
- const read_path = geoJsonName(targetdir + "_" + layername, x, y, z);
- if (!fs.existsSync(read_path)) {
- continue;
- }
- const features = JSON.parse(fs.readFileSync(read_path, "UTF-8")).features
- const pointsOnly = features.map(f => {
-
- f.properties["_last_edit:timestamp"] = "1970-01-01"
-
- if (f.geometry.type === "Point") {
- return f
- } else {
- return GeoOperations.centerpoint(f)
- }
-
- })
- allFeatures.push(...pointsOnly)
- }
- }
-
- const featuresDedup = []
- const seen = new Set()
- for (const feature of allFeatures) {
- const id = feature.properties.id
- if (seen.has(id)) {
- continue
- }
- seen.add(id)
- featuresDedup.push(feature)
- }
-
- const geojson = {
- "type": "FeatureCollection",
- "features": featuresDedup
- }
- writeFileSync(targetdir + "_" + layername + "_points.geojson", JSON.stringify(geojson, null, " "))
-}
async function main(args: string[]) {
@@ -335,8 +287,8 @@ async function main(args: string[]) {
console.error("The theme " + theme + " was not found; try one of ", keys);
return
}
-
- const overpass = createOverpassObject(theme)
+ const relationTracker = new RelationsTracker()
+ const overpass = createOverpassObject(theme, relationTracker)
let failed = 0;
do {
@@ -348,21 +300,13 @@ async function main(args: string[]) {
} while (failed > 0)
const extraFeatures = await downloadExtraData(theme);
- postProcess(targetdir, tileRange, theme, extraFeatures)
- splitPerLayer(targetdir, tileRange, theme)
+ const allFeaturesSource = loadAllTiles(targetdir, tileRange, theme, extraFeatures)
+ postProcess(allFeaturesSource, theme, relationTracker, targetdir)
- if (args[7] === "--generate-point-overview") {
- const targetLayers = args[8].split(",")
- for (const targetLayer of targetLayers) {
- if (!theme.layers.some(l => l.id === targetLayer)) {
- throw "Target layer " + targetLayer + " not found, did you mistype the name? Found layers are: " + theme.layers.map(l => l.id).join(",")
- }
- createOverview(targetdir, tileRange, zoomlevel, targetLayer)
- }
- }
}
let args = [...process.argv]
args.splice(0, 2)
-main(args);
\ No newline at end of file
+main(args);
+console.log("All done!")
\ No newline at end of file