forked from MapComplete/MapComplete
		
	Merge branch 'develop' into alpha
This commit is contained in:
		
						commit
						aa1b33f9e4
					
				
					 11 changed files with 103 additions and 63 deletions
				
			
		|  | @ -426,10 +426,10 @@ export class InitUiElements { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |         const clustering = State.state.layoutToUse.clustering | ||||||
|         const clusterCounter = TileHierarchyAggregator.createHierarchy() |         const clusterCounter = TileHierarchyAggregator.createHierarchy() | ||||||
|         new ShowDataLayer({ |         new ShowDataLayer({ | ||||||
|             features: clusterCounter.getCountsForZoom(State.state.locationControl, State.state.layoutToUse.clustering.minNeededElements), |             features: clusterCounter.getCountsForZoom(clustering, State.state.locationControl, State.state.layoutToUse.clustering.minNeededElements), | ||||||
|             leafletMap: State.state.leafletMap, |             leafletMap: State.state.leafletMap, | ||||||
|             layerToShow: ShowTileInfo.styling, |             layerToShow: ShowTileInfo.styling, | ||||||
|             enablePopups: false |             enablePopups: false | ||||||
|  | @ -440,7 +440,7 @@ export class InitUiElements { | ||||||
| 
 | 
 | ||||||
|                 clusterCounter.addTile(source) |                 clusterCounter.addTile(source) | ||||||
| 
 | 
 | ||||||
|                 const clustering = State.state.layoutToUse.clustering |                 // Do show features indicates if the 'showDataLayer' should be shown
 | ||||||
|                 const doShowFeatures = source.features.map( |                 const doShowFeatures = source.features.map( | ||||||
|                     f => { |                     f => { | ||||||
|                         const z = State.state.locationControl.data.zoom |                         const z = State.state.locationControl.data.zoom | ||||||
|  | @ -449,6 +449,18 @@ export class InitUiElements { | ||||||
|                             return false; |                             return false; | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|  |                         const bounds = State.state.currentBounds.data | ||||||
|  |                         if(bounds === undefined){ | ||||||
|  |                             // Map is not yet displayed
 | ||||||
|  |                             return false; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         if (!source.bbox.overlapsWith(bounds)) { | ||||||
|  |                             // Not within range -> features are hidden
 | ||||||
|  |                             return false | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|                         if (z < source.layer.layerDef.minzoom) { |                         if (z < source.layer.layerDef.minzoom) { | ||||||
|                             // Layer is always hidden for this zoom level
 |                             // Layer is always hidden for this zoom level
 | ||||||
|                             return false; |                             return false; | ||||||
|  | @ -473,20 +485,12 @@ export class InitUiElements { | ||||||
|                             } |                             } | ||||||
| 
 | 
 | ||||||
|                             if (clusterCounter.getTile(Tiles.tile_index(tileZ, tileX, tileY))?.totalValue > clustering.minNeededElements) { |                             if (clusterCounter.getTile(Tiles.tile_index(tileZ, tileX, tileY))?.totalValue > clustering.minNeededElements) { | ||||||
|  |                                 // To much elements
 | ||||||
|                                 return false |                                 return false | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|                         const bounds = State.state.currentBounds.data |  | ||||||
|                         if(bounds === undefined){ |  | ||||||
|                             // Map is not yet displayed
 |  | ||||||
|                             return false; |  | ||||||
|                         } |  | ||||||
|                         if (!source.bbox.overlapsWith(bounds)) { |  | ||||||
|                             // Not within range
 |  | ||||||
|                             return false |  | ||||||
|                         } |  | ||||||
|                        |                        | ||||||
|                         return true |                         return true | ||||||
|                     }, [State.state.currentBounds, source.layer.isDisplayed] |                     }, [State.state.currentBounds, source.layer.isDisplayed] | ||||||
|  |  | ||||||
|  | @ -21,7 +21,9 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource { | ||||||
|             throw "Invalid layer: geojsonSource expected" |             throw "Invalid layer: geojsonSource expected" | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         const whitelistUrl = source.geojsonSource.replace("{z}_{x}_{y}.geojson", "overview.json") |         const whitelistUrl = source.geojsonSource | ||||||
|  |             .replace("{z}", ""+source.geojsonZoomLevel) | ||||||
|  |             .replace("{x}_{y}.geojson", "overview.json") | ||||||
|             .replace("{layer}",layer.layerDef.id) |             .replace("{layer}",layer.layerDef.id) | ||||||
|          |          | ||||||
|         let whitelist = undefined |         let whitelist = undefined | ||||||
|  | @ -46,7 +48,8 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource { | ||||||
|                 if(whitelist !== undefined){ |                 if(whitelist !== undefined){ | ||||||
|                     const isWhiteListed = whitelist.get(zxy[1])?.has(zxy[2]) |                     const isWhiteListed = whitelist.get(zxy[1])?.has(zxy[2]) | ||||||
|                     if(!isWhiteListed){ |                     if(!isWhiteListed){ | ||||||
|                         return undefined; |                         console.log("Not whitelisted:",zxy, isWhiteListed, whitelist) | ||||||
|  |                     //    return undefined;
 | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                  |                  | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; | import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; | ||||||
| import {LayerConfigJson} from "./LayerConfigJson"; | import {LayerConfigJson} from "./LayerConfigJson"; | ||||||
| import UnitConfigJson from "./UnitConfigJson"; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Defines the entire theme. |  * Defines the entire theme. | ||||||
|  | @ -250,7 +249,7 @@ export interface LayoutConfigJson { | ||||||
|      * If set to [[lat0, lon0], [lat1, lon1]], the map will not scroll outside of those bounds. |      * If set to [[lat0, lon0], [lat1, lon1]], the map will not scroll outside of those bounds. | ||||||
|      * Off by default, which will enable panning to the entire world |      * Off by default, which will enable panning to the entire world | ||||||
|      */ |      */ | ||||||
|     lockLocation?: boolean | [[number, number], [number, number]]; |     lockLocation?: boolean | [[number, number], [number, number]] | number[][]; | ||||||
| 
 | 
 | ||||||
|     enableUserBadge?: boolean; |     enableUserBadge?: boolean; | ||||||
|     enableShareScreen?: boolean; |     enableShareScreen?: boolean; | ||||||
|  |  | ||||||
|  | @ -29,7 +29,7 @@ export default class LayoutConfig { | ||||||
|     public layers: LayerConfig[]; |     public layers: LayerConfig[]; | ||||||
|     public readonly clustering?: { |     public readonly clustering?: { | ||||||
|         maxZoom: number, |         maxZoom: number, | ||||||
|         minNeededElements: number |         minNeededElements: number, | ||||||
|     }; |     }; | ||||||
|     public readonly hideFromOverview: boolean; |     public readonly hideFromOverview: boolean; | ||||||
|     public lockLocation: boolean | [[number, number], [number, number]]; |     public lockLocation: boolean | [[number, number], [number, number]]; | ||||||
|  | @ -137,12 +137,17 @@ export default class LayoutConfig { | ||||||
| 
 | 
 | ||||||
|         this.clustering = { |         this.clustering = { | ||||||
|             maxZoom: 16, |             maxZoom: 16, | ||||||
|             minNeededElements: 25 |             minNeededElements: 25, | ||||||
|         }; |         }; | ||||||
|         if (json.clustering) { |         if(json.clustering === false){ | ||||||
|  |             this.clustering = { | ||||||
|  |                 maxZoom: 0, | ||||||
|  |                 minNeededElements: 100000, | ||||||
|  |             }; | ||||||
|  |         }else         if (json.clustering) { | ||||||
|             this.clustering = { |             this.clustering = { | ||||||
|                 maxZoom: json.clustering.maxZoom ?? 18, |                 maxZoom: json.clustering.maxZoom ?? 18, | ||||||
|                 minNeededElements: json.clustering.minNeededElements ?? 25 |                 minNeededElements: json.clustering.minNeededElements ?? 25, | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -151,7 +156,7 @@ export default class LayoutConfig { | ||||||
|         if (json.hideInOverview) { |         if (json.hideInOverview) { | ||||||
|             throw "The json for " + this.id + " contains a 'hideInOverview'. Did you mean hideFromOverview instead?" |             throw "The json for " + this.id + " contains a 'hideInOverview'. Did you mean hideFromOverview instead?" | ||||||
|         } |         } | ||||||
|         this.lockLocation = json.lockLocation ?? undefined; |         this.lockLocation = <[[number, number], [number, number]]> json.lockLocation ?? undefined; | ||||||
|         this.enableUserBadge = json.enableUserBadge ?? true; |         this.enableUserBadge = json.enableUserBadge ?? true; | ||||||
|         this.enableShareScreen = json.enableShareScreen ?? true; |         this.enableShareScreen = json.enableShareScreen ?? true; | ||||||
|         this.enableMoreQuests = json.enableMoreQuests ?? true; |         this.enableMoreQuests = json.enableMoreQuests ?? true; | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ export class TileHierarchyAggregator implements FeatureSource { | ||||||
|     public readonly name; |     public readonly name; | ||||||
| 
 | 
 | ||||||
|     private readonly featuresStatic = [] |     private readonly featuresStatic = [] | ||||||
|     private readonly featureProperties: { count: string, tileId: string, id: string }; |     private readonly featureProperties: { count: string, kilocount: string, tileId: string, id: string }; | ||||||
| 
 | 
 | ||||||
|     private constructor(parent: TileHierarchyAggregator, z: number, x: number, y: number) { |     private constructor(parent: TileHierarchyAggregator, z: number, x: number, y: number) { | ||||||
|         this._parent = parent; |         this._parent = parent; | ||||||
|  | @ -36,7 +36,8 @@ export class TileHierarchyAggregator implements FeatureSource { | ||||||
|         const totals = { |         const totals = { | ||||||
|             id: ""+this._tileIndex, |             id: ""+this._tileIndex, | ||||||
|             tileId: ""+this._tileIndex, |             tileId: ""+this._tileIndex, | ||||||
|             count: ""+0 |             count: `0`, | ||||||
|  |             kilocount: "0" | ||||||
|         } |         } | ||||||
|         this.featureProperties = totals |         this.featureProperties = totals | ||||||
| 
 | 
 | ||||||
|  | @ -108,6 +109,7 @@ export class TileHierarchyAggregator implements FeatureSource { | ||||||
|             this.features.setData(TileHierarchyAggregator.empty) |             this.features.setData(TileHierarchyAggregator.empty) | ||||||
|         } else { |         } else { | ||||||
|             this.featureProperties.count = "" + total; |             this.featureProperties.count = "" + total; | ||||||
|  |             this.featureProperties.kilocount = "" +Math.floor(total/1000); | ||||||
|             this.features.data = this.featuresStatic |             this.features.data = this.featuresStatic | ||||||
|             this.features.ping() |             this.features.ping() | ||||||
|         } |         } | ||||||
|  | @ -153,13 +155,18 @@ export class TileHierarchyAggregator implements FeatureSource { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     getCountsForZoom(locationControl: UIEventSource<{ zoom : number }>, cutoff: number = 0) : FeatureSource{ |     getCountsForZoom(clusteringConfig: {maxZoom: number}, locationControl: UIEventSource<{ zoom : number }>, cutoff: number = 0) : FeatureSource{ | ||||||
|         const self = this |         const self = this | ||||||
|          |         const empty = [] | ||||||
|         return new StaticFeatureSource( |         return new StaticFeatureSource( | ||||||
|             locationControl.map(loc => { |             locationControl.map(loc => { | ||||||
|                 const features = [] |  | ||||||
|                 const targetZoom = loc.zoom |                 const targetZoom = loc.zoom | ||||||
|  |                  | ||||||
|  |                 if(targetZoom > clusteringConfig.maxZoom){ | ||||||
|  |                     return empty | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  |                 const features = [] | ||||||
|                 self.visitSubTiles(aggr => { |                 self.visitSubTiles(aggr => { | ||||||
|                     if(aggr.totalValue < cutoff) { |                     if(aggr.totalValue < cutoff) { | ||||||
|                         return false |                         return false | ||||||
|  |  | ||||||
							
								
								
									
										26
									
								
								Utils.ts
									
										
									
									
									
								
							
							
						
						
									
										26
									
								
								Utils.ts
									
										
									
									
									
								
							|  | @ -78,18 +78,6 @@ export class Utils { | ||||||
|         return res; |         return res; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     static DoEvery(millis: number, f: (() => void)) { |  | ||||||
|         if (Utils.runningFromConsole) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         window.setTimeout( |  | ||||||
|             function () { |  | ||||||
|                 f(); |  | ||||||
|                 Utils.DoEvery(millis, f); |  | ||||||
|             } |  | ||||||
|             , millis) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static NoNull<T>(array: T[]): T[] { |     public static NoNull<T>(array: T[]): T[] { | ||||||
|         const ls: T[] = []; |         const ls: T[] = []; | ||||||
|         for (const t of array) { |         for (const t of array) { | ||||||
|  | @ -448,5 +436,19 @@ export class Utils { | ||||||
|             window.setTimeout(resolve, timeMillis); |             window.setTimeout(resolve, timeMillis); | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  |      | ||||||
|  |     public static toHumanTime(seconds): string{ | ||||||
|  |         seconds = Math.floor(seconds) | ||||||
|  |         let minutes = Math.floor(seconds / 60) | ||||||
|  |         seconds = seconds % 60 | ||||||
|  |         let hours = Math.floor(minutes / 60) | ||||||
|  |         minutes = minutes % 60 | ||||||
|  |         let days = Math.floor(hours / 24) | ||||||
|  |         hours = hours % 24 | ||||||
|  |         if(days > 0){ | ||||||
|  |             return days+"days"+" "+hours+"h" | ||||||
|  |         } | ||||||
|  |             return hours+":"+Utils.TwoDigits(minutes)+":"+Utils.TwoDigits(seconds) | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -28,8 +28,8 @@ | ||||||
|         "render": "<div class='rounded-full text-xl font-bold' style='width: 2rem; height: 2rem; background: white'>{count}</div>", |         "render": "<div class='rounded-full text-xl font-bold' style='width: 2rem; height: 2rem; background: white'>{count}</div>", | ||||||
|         "mappings": [ |         "mappings": [ | ||||||
|             { |             { | ||||||
|                 "if": "count>99", |                 "if": "count>1000", | ||||||
|                 "then": "<div class='rounded-full text-xl font-bold flex flex-col' style='width: 2.5rem; height: 2.5rem; background: white'>>99</div>" |                 "then": "<div class='rounded-full text-xl font-bold flex flex-col' style='width: 2.5rem; height: 2.5rem; background: white'>{kilocount}K</div>" | ||||||
|             } |             } | ||||||
|         ] |         ] | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -32,8 +32,8 @@ | ||||||
|   "enablePdfDownload": true, |   "enablePdfDownload": true, | ||||||
|   "enableDownload": true, |   "enableDownload": true, | ||||||
|   "hideFromOverview": true, |   "hideFromOverview": true, | ||||||
|   "clustering": { |  | ||||||
|     "#": "Disable clustering for this theme", |     "#": "Disable clustering for this theme", | ||||||
|  |   "clustering": { | ||||||
|     "maxZoom": 0 |     "maxZoom": 0 | ||||||
|   }, |   }, | ||||||
|   "layers": [ |   "layers": [ | ||||||
|  | @ -112,7 +112,7 @@ | ||||||
|           "geoJsonZoomLevel": 12, |           "geoJsonZoomLevel": 12, | ||||||
|           "isOsmCache": true |           "isOsmCache": true | ||||||
|         }, |         }, | ||||||
|         "minzoom": "13", |         "minzoom": 10, | ||||||
|         "icon": { |         "icon": { | ||||||
|           "render": "circle:#FE6F32;./assets/themes/natuurpunt/trail.svg", |           "render": "circle:#FE6F32;./assets/themes/natuurpunt/trail.svg", | ||||||
|           "mappings": [ |           "mappings": [ | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								index.ts
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								index.ts
									
										
									
									
									
								
							|  | @ -63,7 +63,6 @@ if (path !== "index.html" && path !== "") { | ||||||
| defaultLayout = QueryParameters.GetQueryParameter("layout", defaultLayout, "The layout to load into MapComplete").data; | defaultLayout = QueryParameters.GetQueryParameter("layout", defaultLayout, "The layout to load into MapComplete").data; | ||||||
| let layoutToUse: LayoutConfig = AllKnownLayouts.allKnownLayouts.get(defaultLayout.toLowerCase()); | let layoutToUse: LayoutConfig = AllKnownLayouts.allKnownLayouts.get(defaultLayout.toLowerCase()); | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| const userLayoutParam = QueryParameters.GetQueryParameter("userlayout", "false", "If not 'false', a custom (non-official) theme is loaded. This custom layout can be done in multiple ways: \n\n- The hash of the URL contains a base64-encoded .json-file containing the theme definition\n- The hash of the URL contains a lz-compressed .json-file, as generated by the custom theme generator\n- The parameter itself is an URL, in which case that URL will be downloaded. It should point to a .json of a theme"); | const userLayoutParam = QueryParameters.GetQueryParameter("userlayout", "false", "If not 'false', a custom (non-official) theme is loaded. This custom layout can be done in multiple ways: \n\n- The hash of the URL contains a base64-encoded .json-file containing the theme definition\n- The hash of the URL contains a lz-compressed .json-file, as generated by the custom theme generator\n- The parameter itself is an URL, in which case that URL will be downloaded. It should point to a .json of a theme"); | ||||||
| 
 | 
 | ||||||
| // Workaround/legacy to keep the old paramters working as I renamed some of them
 | // Workaround/legacy to keep the old paramters working as I renamed some of them
 | ||||||
|  |  | ||||||
|  | @ -36,9 +36,6 @@ | ||||||
|         "splitTitle": "Choose on the map where to split this road", |         "splitTitle": "Choose on the map where to split this road", | ||||||
|         "hasBeenSplit": "This way has been split" |         "hasBeenSplit": "This way has been split" | ||||||
|     }, |     }, | ||||||
|     "multi_apply": { |  | ||||||
|         "autoApply": "When changing the attributes {attr_names}, these attributes will automatically be changed on {count} other objects too" |  | ||||||
|     }, |  | ||||||
|     "delete": { |     "delete": { | ||||||
|         "delete": "Delete", |         "delete": "Delete", | ||||||
|         "cancel": "Cancel", |         "cancel": "Cancel", | ||||||
|  | @ -123,7 +120,7 @@ | ||||||
|         }, |         }, | ||||||
|         "morescreen": { |         "morescreen": { | ||||||
|             "intro": "<h3>More thematic maps?</h3>Do you enjoy collecting geodata? <br/>There are more themes available.", |             "intro": "<h3>More thematic maps?</h3>Do you enjoy collecting geodata? <br/>There are more themes available.", | ||||||
|             "requestATheme": "If you want a custom-built quest, request it in the issue tracker", |             "requestATheme": "If you want a custom-built theme, request it in the issue tracker", | ||||||
|             "streetcomplete": "Another, similar application is <a href='https://play.google.com/store/apps/details?id=de.westnordost.streetcomplete' class='underline hover:text-blue-800' class='underline hover:text-blue-800' target='_blank'>StreetComplete</a>.", |             "streetcomplete": "Another, similar application is <a href='https://play.google.com/store/apps/details?id=de.westnordost.streetcomplete' class='underline hover:text-blue-800' class='underline hover:text-blue-800' target='_blank'>StreetComplete</a>.", | ||||||
|             "createYourOwnTheme": "Create your own MapComplete theme from scratch" |             "createYourOwnTheme": "Create your own MapComplete theme from scratch" | ||||||
|         }, |         }, | ||||||
|  | @ -253,5 +250,8 @@ | ||||||
|         "tos": "If you create a review, you agree to <a href='https://mangrove.reviews/terms' target='_blank'>the TOS and privacy policy of Mangrove.reviews</a>", |         "tos": "If you create a review, you agree to <a href='https://mangrove.reviews/terms' target='_blank'>the TOS and privacy policy of Mangrove.reviews</a>", | ||||||
|         "attribution": "Reviews are powered by <a href='https://mangrove.reviews/' target='_blank'>Mangrove Reviews</a> and are available under <a href='https://mangrove.reviews/terms#8-licensing-of-content' target='_blank'>CC-BY 4.0</a>.", |         "attribution": "Reviews are powered by <a href='https://mangrove.reviews/' target='_blank'>Mangrove Reviews</a> and are available under <a href='https://mangrove.reviews/terms#8-licensing-of-content' target='_blank'>CC-BY 4.0</a>.", | ||||||
|         "plz_login": "Login to leave a review" |         "plz_login": "Login to leave a review" | ||||||
|  |     }, | ||||||
|  |     "multi_apply": { | ||||||
|  |         "autoApply": "When changing the attributes {attr_names}, these attributes will automatically be changed on {count} other objects too" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -20,6 +20,7 @@ import FeatureSource, {FeatureSourceForLayer} from "../Logic/FeatureSource/Featu | ||||||
| import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"; | import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"; | ||||||
| import TiledFeatureSource from "../Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource"; | import TiledFeatureSource from "../Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource"; | ||||||
| import Constants from "../Models/Constants"; | import Constants from "../Models/Constants"; | ||||||
|  | import {GeoOperations} from "../Logic/GeoOperations"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ScriptUtils.fixUtils() | ScriptUtils.fixUtils() | ||||||
|  | @ -73,6 +74,7 @@ async function downloadRaw(targetdir: string, r: TileRange, theme: LayoutConfig, | ||||||
|     let downloaded = 0 |     let downloaded = 0 | ||||||
|     let failed = 0 |     let failed = 0 | ||||||
|     let skipped = 0 |     let skipped = 0 | ||||||
|  |     const startTime = new Date().getTime() | ||||||
|     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++) { | ||||||
|             downloaded++; |             downloaded++; | ||||||
|  | @ -82,7 +84,11 @@ async function downloadRaw(targetdir: string, r: TileRange, theme: LayoutConfig, | ||||||
|                 skipped++ |                 skipped++ | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|             console.log("x:", (x - r.xstart), "/", (r.xend - r.xstart), "; y:", (y - r.ystart), "/", (r.yend - r.ystart), "; total: ", downloaded, "/", r.total, "failed: ", failed, "skipped: ", skipped) |             const runningSeconds = (new Date().getTime() - startTime) / 1000 | ||||||
|  |             const resting = failed + (r.total - downloaded) | ||||||
|  |             const perTile=   (runningSeconds / (downloaded - skipped)) | ||||||
|  |             const estimated =Math.floor(resting * perTile) | ||||||
|  |             console.log("total: ", downloaded, "/", r.total, "failed: ", failed, "skipped: ", skipped, "running time: ",Utils.toHumanTime(runningSeconds)+"s", "estimated left: ", Utils.toHumanTime(estimated), "("+Math.floor(perTile)+"s/tile)") | ||||||
| 
 | 
 | ||||||
|             const boundsArr = Tiles.tile_bounds(r.zoomlevel, x, y) |             const boundsArr = Tiles.tile_bounds(r.zoomlevel, x, y) | ||||||
|             const bounds = { |             const bounds = { | ||||||
|  | @ -91,7 +97,7 @@ async function downloadRaw(targetdir: string, r: TileRange, theme: LayoutConfig, | ||||||
|                 east: Math.max(boundsArr[0][1], boundsArr[1][1]), |                 east: Math.max(boundsArr[0][1], boundsArr[1][1]), | ||||||
|                 west: Math.min(boundsArr[0][1], boundsArr[1][1]) |                 west: Math.min(boundsArr[0][1], boundsArr[1][1]) | ||||||
|             } |             } | ||||||
|             const overpass = createOverpassObject(theme, relationTracker, Constants.defaultOverpassUrls[(downloaded + failed) % Constants.defaultOverpassUrls.length]) |             const overpass = createOverpassObject(theme, relationTracker, Constants.defaultOverpassUrls[(failed) % Constants.defaultOverpassUrls.length]) | ||||||
|             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 + "]") | ||||||
| 
 | 
 | ||||||
|             try { |             try { | ||||||
|  | @ -170,11 +176,11 @@ function loadAllTiles(targetdir: string, r: TileRange, theme: LayoutConfig, extr | ||||||
| /** | /** | ||||||
|  * Load all the tiles into memory from disk |  * Load all the tiles into memory from disk | ||||||
|  */ |  */ | ||||||
| function postProcess(allFeatures: FeatureSource, theme: LayoutConfig, relationsTracker: RelationsTracker, targetdir: string) { | function sliceToTiles(allFeatures: FeatureSource, theme: LayoutConfig, relationsTracker: RelationsTracker, targetdir: string, pointsOnlyLayers: string[]) { | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     function handleLayer(source: FeatureSourceForLayer) { |     function handleLayer(source: FeatureSourceForLayer) { | ||||||
|         const layer = source.layer.layerDef; |         const layer = source.layer.layerDef; | ||||||
|  |         const targetZoomLevel = layer.source.geojsonZoomLevel ?? 0 | ||||||
|  |          | ||||||
|         const layerId = layer.id |         const layerId = layer.id | ||||||
|         if (layer.source.isOsmCacheLayer !== true) { |         if (layer.source.isOsmCacheLayer !== true) { | ||||||
|             return; |             return; | ||||||
|  | @ -200,13 +206,10 @@ function postProcess(allFeatures: FeatureSource, theme: LayoutConfig, relationsT | ||||||
|         // At this point, we have all the features of the entire area.
 |         // 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
 |         // However, we want to export them per tile of a fixed size, so we use a dynamicTileSOurce to split it up
 | ||||||
|         TiledFeatureSource.createHierarchy(source, { |         TiledFeatureSource.createHierarchy(source, { | ||||||
|             minZoomLevel: 14, |             minZoomLevel: targetZoomLevel, | ||||||
|             maxZoomLevel: 14, |             maxZoomLevel: targetZoomLevel, | ||||||
|             maxFeatureCount: undefined, |             maxFeatureCount: undefined, | ||||||
|             registerTile: tile => { |             registerTile: tile => { | ||||||
|                 if (tile.z < 12) { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 if (tile.features.data.length === 0) { |                 if (tile.features.data.length === 0) { | ||||||
|                     return |                     return | ||||||
|                 } |                 } | ||||||
|  | @ -229,7 +232,7 @@ function postProcess(allFeatures: FeatureSource, theme: LayoutConfig, relationsT | ||||||
| 
 | 
 | ||||||
|         // All the tiles are written at this point
 |         // All the tiles are written at this point
 | ||||||
|         // Only thing left to do is to create the index
 |         // Only thing left to do is to create the index
 | ||||||
|         const path = targetdir + "_" + layerId + "_overview.json" |         const path = targetdir + "_" + layerId + "_" + targetZoomLevel + "_overview.json" | ||||||
|         const perX = {} |         const perX = {} | ||||||
|         createdTiles.map(i => Tiles.tile_from_index(i)).forEach(([z, x, y]) => { |         createdTiles.map(i => Tiles.tile_from_index(i)).forEach(([z, x, y]) => { | ||||||
|             const key = "" + x |             const key = "" + x | ||||||
|  | @ -240,7 +243,18 @@ function postProcess(allFeatures: FeatureSource, theme: LayoutConfig, relationsT | ||||||
|         }) |         }) | ||||||
|         writeFileSync(path, JSON.stringify(perX)) |         writeFileSync(path, JSON.stringify(perX)) | ||||||
| 
 | 
 | ||||||
| 
 |         // And, if needed, to create a points-only layer
 | ||||||
|  |         if(pointsOnlyLayers.indexOf(layer.id) >= 0){ | ||||||
|  |             const features = source.features.data.map(f => f.feature) | ||||||
|  |             const points = features.map(feature => GeoOperations.centerpoint(feature)) | ||||||
|  |             console.log("Writing points overview for ", layerId) | ||||||
|  |             const targetPath = targetdir+"_"+layerId+"_points.geojson" | ||||||
|  |             // This is the geojson file containing all features for this tile
 | ||||||
|  |             writeFileSync(targetPath, JSON.stringify({ | ||||||
|  |                 type: "FeatureCollection", | ||||||
|  |                 features: points | ||||||
|  |             }, null, " ")) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     new PerLayerFeatureSourceSplitter( |     new PerLayerFeatureSourceSplitter( | ||||||
|  | @ -255,10 +269,11 @@ function postProcess(allFeatures: FeatureSource, theme: LayoutConfig, relationsT | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| async function main(args: string[]) { | async function main(args: string[]) { | ||||||
| 
 | 
 | ||||||
|     if (args.length == 0) { |     if (args.length == 0) { | ||||||
|         console.error("Expected arguments are: theme zoomlevel targetdirectory lat0 lon0 lat1 lon1 [--generate-point-overview layer-name]") |         console.error("Expected arguments are: theme zoomlevel targetdirectory lat0 lon0 lat1 lon1 [--generate-point-overview layer-name,layer-name,...]") | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     const themeName = args[0] |     const themeName = args[0] | ||||||
|  | @ -269,6 +284,12 @@ async function main(args: string[]) { | ||||||
|     const lat1 = Number(args[5]) |     const lat1 = Number(args[5]) | ||||||
|     const lon1 = Number(args[6]) |     const lon1 = Number(args[6]) | ||||||
|      |      | ||||||
|  |     let generatePointLayersFor = [] | ||||||
|  |     if(args[7] == "--generate-point-overview"){ | ||||||
|  |         generatePointLayersFor = args[8].split(",") | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | 
 | ||||||
|     const tileRange = Tiles.TileRangeBetween(zoomlevel, lat0, lon0, lat1, lon1) |     const tileRange = Tiles.TileRangeBetween(zoomlevel, lat0, lon0, lat1, lon1) | ||||||
| 
 | 
 | ||||||
|     const theme = AllKnownLayouts.allKnownLayouts.get(themeName) |     const theme = AllKnownLayouts.allKnownLayouts.get(themeName) | ||||||
|  | @ -293,7 +314,7 @@ async function main(args: string[]) { | ||||||
| 
 | 
 | ||||||
|     const extraFeatures = await downloadExtraData(theme); |     const extraFeatures = await downloadExtraData(theme); | ||||||
|     const allFeaturesSource = loadAllTiles(targetdir, tileRange, theme, extraFeatures) |     const allFeaturesSource = loadAllTiles(targetdir, tileRange, theme, extraFeatures) | ||||||
|     postProcess(allFeaturesSource, theme, relationTracker, targetdir) |     sliceToTiles(allFeaturesSource, theme, relationTracker, targetdir, generatePointLayersFor) | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue