Remove generate cache scripts

This commit is contained in:
Pieter Vander Vennet 2024-02-23 12:05:08 +01:00
parent 8cf65c7bf5
commit f3db4b34c9
17 changed files with 444 additions and 8231 deletions

View file

@ -22,10 +22,14 @@
"render": {
"en": "POI with image"
},
"mappings": [{
"if": "name~*",
"then": {"*": "name"}
}]
"mappings": [
{
"if": "name~*",
"then": {
"*": "name"
}
}
]
},
"name": {
"en": "Items with at least one image"

View file

@ -3,7 +3,9 @@
"description": "Special layer which shows `count`",
"source": "special",
"title": {
"render": {"en": "Summary"}
"render": {
"en": "Summary"
}
},
"tagRenderings": [
"all_tags"

View file

@ -44,5 +44,4 @@
"ghost_bike"
],
"widenFactor": 5
}

View file

@ -58,7 +58,7 @@
{
"builtin": "elongated_coin",
"override": {
"doCount": false,
"doCount": false,
"name": null,
"minzoom": 18
}
@ -66,7 +66,7 @@
{
"builtin": "ticket_machine",
"override": {
"doCount": false,
"doCount": false,
"name": null,
"minzoom": 18
}

View file

@ -7100,6 +7100,15 @@
}
}
},
"souvenir_note": {
"tagRenderings": {
"designs": {
"freeform": {
"placeholder": "Nombre de dissenys (p. e. 5)"
}
}
}
},
"speed_camera": {
"description": "Capa que mostra càmeres de velocitat",
"name": "Càmera de velocitat",

View file

@ -7396,6 +7396,15 @@
}
}
},
"souvenir_note": {
"tagRenderings": {
"designs": {
"freeform": {
"placeholder": "Počet vzorů (např. 5)"
}
}
}
},
"speed_camera": {
"description": "Vrstva zobrazující rychlostní radary",
"name": "Rychlostní radar",

View file

@ -8563,6 +8563,15 @@
"render": "Geschwindigkeitsreduzierte Straße"
}
},
"souvenir_note": {
"tagRenderings": {
"designs": {
"freeform": {
"placeholder": "Motivanzahl (z.B. 5)"
}
}
}
},
"speed_camera": {
"description": "Ebene mit Blitzern",
"name": "Blitzer",

View file

@ -5658,6 +5658,12 @@
"render": "Information board"
}
},
"item_with_image": {
"name": "Items with at least one image",
"title": {
"render": "POI with image"
}
},
"kerbs": {
"description": "A layer showing kerbs.",
"filter": {
@ -6993,6 +6999,101 @@
"render": "Playground"
}
},
"playground_equipment": {
"description": "Layer showing playground equipment",
"name": "Playground equipment",
"presets": {
"0": {
"description": "An exact type is asked later",
"title": "a playground device"
}
},
"tagRenderings": {
"type": {
"freeform": {
"placeholder": "Type of device"
},
"mappings": {
"0": {
"then": "This is a swing"
},
"1": {
"then": "This is a structure consisting of several connected playground devices"
},
"2": {
"then": "This is a slide"
},
"3": {
"then": "This is a sand pit"
},
"4": {
"then": "This is a spring rider"
},
"5": {
"then": "This is a climbing frame"
},
"6": {
"then": "This is a seesaw"
},
"7": {
"then": "This is a playhouse"
},
"8": {
"then": "This is a roundabout"
},
"9": {
"then": "This is a basket swing"
},
"10": {
"then": "This is a zip wire"
},
"11": {
"then": "This is a horizontal bar"
},
"12": {
"then": "This is a hopscotch"
},
"13": {
"then": "This is a splash pad"
},
"14": {
"then": "This is a climbing wall"
},
"15": {
"then": "This is a map"
},
"16": {
"then": "This is a bridge (either as a standalone device or as part of a larger structure)"
},
"17": {
"then": "This is a bouncy cushion"
},
"18": {
"then": "This is an activity panel"
},
"19": {
"then": "This is a teen shelter"
},
"20": {
"then": "This is a funnel used to play with funnel ball"
},
"21": {
"then": "This is a spinning circle"
}
},
"question": "What kind of device is this?",
"render": "This is a {playground}"
},
"wheelchair-access": {
"override": {
"question": "Is this device accessible by wheelchair?"
}
}
},
"title": {
"render": "Playground device"
}
},
"postboxes": {
"description": "The layer showing postboxes.",
"name": "Postboxes",
@ -7007,6 +7108,43 @@
},
"postoffices": {
"description": "A layer showing post offices.",
"filter": {
"1": {
"options": {
"0": {
"question": "Offers letter posting"
}
}
},
"2": {
"options": {
"0": {
"question": "Offers parcel posting"
}
}
},
"3": {
"options": {
"0": {
"question": "Offers pickup of missed parcels"
}
}
},
"4": {
"options": {
"0": {
"question": "Accepts pickup of parcels sent here"
}
}
},
"5": {
"options": {
"0": {
"question": "Sells stamps"
}
}
}
},
"name": "Post offices",
"presets": {
"0": {
@ -8563,6 +8701,125 @@
"render": "Slow road"
}
},
"souvenir_coin": {
"description": "Layer showing machines selling souvenir coins",
"name": "Souvenir Coin Machines",
"presets": {
"0": {
"description": "Add a machine selling souvenir coins",
"title": "a souvenir coin machine"
}
},
"tagRenderings": {
"charge": {
"freeform": {
"placeholder": "Cost (e.g. 2 EUR)"
},
"mappings": {
"0": {
"then": "A souvenir coin costs 2 euro"
}
},
"question": "How much does a souvenir coin cost?",
"render": "A souvenir coins costs {charge}"
},
"designs": {
"override": {
"mappings": {
"0": {
"then": "This machine has one design available"
},
"1": {
"then": "This machine has two designs available"
},
"2": {
"then": "This machine has three designs available"
},
"3": {
"then": "This machine has four designs available"
}
},
"render": "This machine has {coin:design_count} designs available"
}
},
"indoor": {
"mappings": {
"0": {
"then": "This machine is located indoors."
},
"1": {
"then": "This machine is located outdoors."
}
},
"question": "Is this machine located indoors?"
}
},
"title": {
"render": "Souvenir Coin Machine"
}
},
"souvenir_note": {
"description": "Layer showing machines selling souvenir banknotes",
"name": "Souvenir Banknote Machines",
"presets": {
"0": {
"description": "Add a machine selling souvenir banknotes",
"title": "a souvenir banknote machine"
}
},
"tagRenderings": {
"charge": {
"freeform": {
"placeholder": "Cost (e.g. 2 EUR)"
},
"mappings": {
"0": {
"then": "A souvenir note costs 2 euro"
},
"1": {
"then": "A souvenir note costs 3 euro"
}
},
"question": "How much does a souvenir note cost?",
"render": "A souvenir note costs {charge}"
},
"designs": {
"freeform": {
"placeholder": "Number of designs (e.g. 5)"
},
"mappings": {
"0": {
"then": "This machine has one design available."
},
"1": {
"then": "This machine has two designs available."
},
"2": {
"then": "This machine has three designs available."
},
"3": {
"then": "This machine has four designs available."
}
},
"question": "How many designs are available?",
"render": "This machine has {note:design_count} designs available."
},
"indoor": {
"mappings": {
"0": {
"then": "This machine is located indoors."
},
"1": {
"then": "This machine is located outdoors."
}
},
"question": "Is this machine located indoors?"
}
},
"title": {
"render": "Souvenir Banknote Machine"
}
},
"speed_camera": {
"description": "Layer showing speed cameras",
"name": "Speed Camera",
@ -9063,6 +9320,11 @@
"render": "Stripclub"
}
},
"summary": {
"title": {
"render": "Summary"
}
},
"surveillance_camera": {
"description": "This layer shows surveillance cameras and allows a contributor to update information and add new cameras",
"name": "Surveillance camera's",

View file

@ -4023,6 +4023,15 @@
}
}
},
"souvenir_note": {
"tagRenderings": {
"designs": {
"freeform": {
"placeholder": "Número de diseños (por ejemplo, 5)"
}
}
}
},
"speed_camera": {
"description": "Capa con cámaras de velocidad",
"name": "Cámara de velocidad",

View file

@ -6065,6 +6065,33 @@
"render": "Speeltuin"
}
},
"playground_equipment": {
"tagRenderings": {
"type": {
"mappings": {
"0": {
"then": "Dit is een schommel"
},
"3": {
"then": "Dit is een zandbak"
},
"4": {
"then": "Dit is een veertoestel"
},
"5": {
"then": "Dit is een klimrek"
},
"6": {
"then": "Dit is een wipwap"
},
"11": {
"then": "Dit is een rekstok"
}
},
"question": "Wat voor speeltoestel is dit?"
}
}
},
"postboxes": {
"description": "Deze laag toont brievenbussen.",
"name": "Brievenbussen",

View file

@ -810,6 +810,71 @@
"description": "A <b>ghost bike</b> is a memorial for a cyclist who died in a traffic accident, in the form of a white bicycle placed permanently near the accident location.<br/><br/>On this map, one can see all the ghost bikes which are known by OpenStreetMap. Is a ghost bike missing? Everyone can add or update information here - you only need to have a (free) OpenStreetMap account. <p>There exists an <a href='https://masto.bike/@ghostbikebot' target='_blank'>automated account on Mastodon which posts a monthly overview of ghost bikes worldwide</a></p>",
"title": "Ghost bikes"
},
"ghostsigns": {
"description": "A map showing disused signs on buildings",
"layers": {
"0": {
"description": "Layer showing disused signs on buildings",
"name": "Ghost Signs",
"presets": {
"0": {
"title": "a ghost sign"
}
},
"tagRenderings": {
"brand": {
"freeform": {
"placeholder": "Business name"
},
"question": "For what business was this sign made?",
"render": "This sign was made for: {brand}"
},
"historic": {
"mappings": {
"0": {
"then": "This is a ghost sign"
},
"1": {
"then": "This is not a ghost sign, answering this will hide the sign from the map"
}
},
"question": "Is this a ghost sign?",
"questionHint": "Is this sign for a business that no longer exists or no longer being maintained?"
},
"inscription": {
"freeform": {
"placeholder": "Text on the sign"
},
"question": "What is the text on the sign?",
"render": "The text on the sign is: {inscription}"
}
},
"title": {
"render": "Ghost Sign"
}
},
"1": {
"override": {
"+tagRenderings": {
"0": {
"mappings": {
"0": {
"then": "This is a ghost sign"
},
"1": {
"then": "This is not a ghost sign"
}
},
"question": "Is this a ghost sign?",
"questionHint": "Is this sign for a business that no longer exists or no longer being maintained?"
}
},
"name": "All advertentie wall paintings"
}
}
},
"title": "Ghost Signs"
},
"grb": {
"description": "This theme is an attempt to help automating the GRB import.",
"layers": {
@ -886,6 +951,10 @@
"description": "On this map, publicly accessible indoor places are shown",
"title": "Indoors"
},
"items_with_image": {
"description": "A map showing all items on OSM which have an image. This theme is a very bad fit for MapComplete as someone is not able to directly add a picture. However, this theme is mostly here to include this all into the database, which'll allow this to quickly fetch images nearby for other features",
"title": "All items with images"
},
"kerbs_and_crossings": {
"description": "A map showing kerbs and crossings.",
"layers": {
@ -1262,6 +1331,32 @@
},
"postboxes": {
"description": "On this map you can find and add data of post offices and post boxes. You can use this map to find where you can mail your next postcard! :)<br/>Spotted an error or is a post box missing? You can edit this map with a free OpenStreetMap account.",
"layers": {
"3": {
"override": {
"+tagRenderings": {
"0": {
"mappings": {
"0": {
"then": "This shop is a post partner"
},
"1": {
"then": "This shop is not a post partner"
}
},
"question": "Is this shop a post partner?"
}
},
"=presets": {
"0": {
"description": "If a shop is not yet on the map and is a post partner, you can add it here.",
"title": "a missing shop that is a post partner"
}
},
"description": "Add a new post partner to the map in an existing shop"
}
}
},
"shortDescription": "A map showing postboxes and post offices",
"title": "Postbox and Post Office Map"
},

View file

@ -721,6 +721,15 @@
"description": "Een <b>Witte Fiets</b> of <b>Spookfiets</b> is een aandenken aan een fietser die bij een verkeersongeval om het leven kwam. Het gaat om een fiets die volledig wit is geschilderd en in de buurt van het ongeval werd geinstalleerd.<br/><br/>Op deze kaart zie je alle witte fietsen die door OpenStreetMap gekend zijn. Ontbreekt er een Witte Fiets of wens je informatie aan te passen? Meld je dan aan met een (gratis) OpenStreetMap account.",
"title": "Witte Fietsen"
},
"ghostsigns": {
"layers": {
"1": {
"override": {
"name": "Alle adverterende muurschilderingen"
}
}
}
},
"grb": {
"description": "Dit thema helpt het GRB importeren.",
"layers": {

View file

@ -60,8 +60,6 @@
"reset:translations": "vite-node scripts/generateTranslations.ts -- --ignore-weblate",
"generate:layouts": "vite-node scripts/generateLayouts.ts",
"generate:docs": "rm -rf Docs/Themes/* && rm -rf Docs/Layers/* && rm -rf Docs/TagInfo && mkdir Docs/TagInfo && vite-node scripts/generateDocs.ts && vite-node scripts/generateTaginfoProjectFiles.ts",
"generate:cache:speelplekken": "npm run generate:layeroverview && vite-node scripts/generateCache.ts -- speelplekken 14 ../MapComplete-data/speelplekken_cache/ 51.20 4.35 51.09 4.56",
"generate:cache:natuurpunt": "npm run generate:layeroverview && vite-node scripts/generateCache.ts -- natuurpunt 12 ../MapComplete-data/natuurpunt_cache/ 50.40 2.1 51.54 6.4 --generate-point-overview nature_reserve,visitor_information_centre",
"generate:layeroverview": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts",
"velopark": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts -- --force --themes=velopark --layers=bike_parking,maproulette_challenge",
"generate:mapcomplete-changes-theme": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts -- --generate-change-map",

View file

@ -1,571 +0,0 @@
/**
* Generates a collection of geojson files based on an overpass query for a given theme
*/
import { Utils } from "../src/Utils"
import { Overpass } from "../src/Logic/Osm/Overpass"
import { existsSync, readFileSync, writeFileSync } from "fs"
import { TagsFilter } from "../src/Logic/Tags/TagsFilter"
import { Or } from "../src/Logic/Tags/Or"
import { AllKnownLayouts } from "../src/Customizations/AllKnownLayouts"
import * as OsmToGeoJson from "osmtogeojson"
import MetaTagging from "../src/Logic/MetaTagging"
import { UIEventSource } from "../src/Logic/UIEventSource"
import { TileRange, Tiles } from "../src/Models/TileRange"
import LayoutConfig from "../src/Models/ThemeConfig/LayoutConfig"
import ScriptUtils from "./ScriptUtils"
import PerLayerFeatureSourceSplitter from "../src/Logic/FeatureSource/PerLayerFeatureSourceSplitter"
import FilteredLayer from "../src/Models/FilteredLayer"
import StaticFeatureSource from "../src/Logic/FeatureSource/Sources/StaticFeatureSource"
import Constants from "../src/Models/Constants"
import { GeoOperations } from "../src/Logic/GeoOperations"
import SimpleMetaTaggers, { ReferencingWaysMetaTagger } from "../src/Logic/SimpleMetaTagger"
import FilteringFeatureSource from "../src/Logic/FeatureSource/Sources/FilteringFeatureSource"
import { Feature } from "geojson"
import { BBox } from "../src/Logic/BBox"
import { FeatureSource } from "../src/Logic/FeatureSource/FeatureSource"
import OsmObjectDownloader from "../src/Logic/Osm/OsmObjectDownloader"
import FeaturePropertiesStore from "../src/Logic/FeatureSource/Actors/FeaturePropertiesStore"
ScriptUtils.fixUtils()
function createOverpassObject(theme: LayoutConfig, backend: string) {
let filters: TagsFilter[] = []
let extraScripts: string[] = []
for (const layer of theme.layers) {
if (typeof layer === "string") {
throw "A layer was not expanded!"
}
if (layer.doNotDownload) {
continue
}
if (!layer.source) {
continue
}
if (layer.source.geojsonSource) {
// This layer defines a geoJson-source
// SHould it be cached?
if (layer.source.isOsmCacheLayer !== true) {
continue
}
}
filters.push(layer.source.osmTags)
}
filters = Utils.NoNull(filters)
extraScripts = Utils.NoNull(extraScripts)
if (filters.length + extraScripts.length === 0) {
throw "Nothing to download! The theme doesn't declare anything to download"
}
return new Overpass(new Or(filters), extraScripts, backend, new UIEventSource<number>(60))
}
function rawJsonName(targetDir: string, x: number, y: number, z: number): string {
return targetDir + "_" + z + "_" + x + "_" + y + ".json"
}
function geoJsonName(targetDir: string, x: number, y: number, z: number): string {
return targetDir + "_" + z + "_" + x + "_" + y + ".geojson"
}
/// Downloads the given tilerange from overpass and saves them to disk
async function downloadRaw(
targetdir: string,
r: TileRange,
theme: LayoutConfig
): Promise<{ failed: number; skipped: number }> {
let downloaded = 0
let failed = 0
let skipped = 0
const startTime = new Date().getTime()
for (let x = r.xstart; x <= r.xend; x++) {
for (let y = r.ystart; y <= r.yend; y++) {
downloaded++
const filename = rawJsonName(targetdir, x, y, r.zoomlevel)
if (existsSync(filename)) {
console.log("Already exists (not downloading again): ", filename)
skipped++
continue
}
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 bounds = {
north: Math.max(boundsArr[0][0], boundsArr[1][0]),
south: Math.min(boundsArr[0][0], boundsArr[1][0]),
east: Math.max(boundsArr[0][1], boundsArr[1][1]),
west: Math.min(boundsArr[0][1], boundsArr[1][1]),
}
const overpass = createOverpassObject(
theme,
Constants.defaultOverpassUrls[failed % Constants.defaultOverpassUrls.length]
)
const url = overpass.buildQuery(
"[bbox:" +
bounds.south +
"," +
bounds.west +
"," +
bounds.north +
"," +
bounds.east +
"]"
)
try {
const json = await Utils.downloadJson(url)
if ((<string>json.remark ?? "").startsWith("runtime error")) {
console.error("Got a runtime error: ", json.remark)
failed++
} else if (json.elements.length === 0) {
console.log("Got an empty response! Writing anyway")
}
console.log(
"Got the response - writing ",
json.elements.length,
" elements to ",
filename
)
writeFileSync(filename, JSON.stringify(json, null, " "))
} catch (err) {
console.log(url)
console.log(
"Could not download - probably hit the rate limit; waiting a bit. (" + err + ")"
)
failed++
await ScriptUtils.sleep(1)
}
}
}
return { failed: failed, skipped: skipped }
}
/*
* 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) {
if (!layer.source?.geojsonSource) {
continue
}
const source = layer.source.geojsonSource
if (layer.source.isOsmCacheLayer !== undefined && layer.source.isOsmCacheLayer !== false) {
// Cached layers are not considered here
continue
}
if (source.startsWith("https://api.openstreetmap.org/api/0.6/notes.json")) {
// We ignore map notes
continue
}
console.log("Downloading extra data: ", source)
await Utils.downloadJson(source).then((json) => allFeatures.push(...json.features))
}
return allFeatures
}
function loadAllTiles(
targetdir: string,
r: TileRange,
theme: LayoutConfig,
extraFeatures: any[]
): FeatureSource {
let allFeatures = [...extraFeatures]
let processed = 0
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)
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
}
// We read the raw OSM-file and convert it to a geojson
const rawOsm = JSON.parse(readFileSync(filename, { encoding: "utf8" }))
// Create and save the geojson file - which is the main chunk of the data
const geojson = OsmToGeoJson.default(rawOsm)
console.log(" which as", geojson.features.length, "features")
allFeatures.push(...geojson.features)
}
}
return StaticFeatureSource.fromGeojson(allFeatures)
}
/**
* Load all the tiles into memory from disk
*/
async function sliceToTiles(
allFeatures: FeatureSource,
theme: LayoutConfig,
targetdir: string,
pointsOnlyLayers: string[],
clip: boolean,
targetzoomLevel: number = 9
) {
const skippedLayers = new Set<string>()
const indexedFeatures: Map<string, any> = new Map<string, any>()
let indexisBuilt = false
const osmObjectDownloader = new OsmObjectDownloader()
function buildIndex() {
for (const f of allFeatures.features.data) {
indexedFeatures.set(f.properties.id, f)
}
indexisBuilt = true
}
function getFeatureById(id) {
if (!indexisBuilt) {
buildIndex()
}
return indexedFeatures.get(id)
}
const flayers: FilteredLayer[] = theme.layers.map((l) => new FilteredLayer(l))
const perLayer = new PerLayerFeatureSourceSplitter(flayers, allFeatures)
for (const [layerId, source] of perLayer.perLayer) {
const layer = flayers.find((flayer) => flayer.layerDef.id === layerId).layerDef
const targetZoomLevel = layer.source.geojsonZoomLevel ?? targetzoomLevel
if (layer.source.geojsonSource && layer.source.isOsmCacheLayer !== true) {
console.log("Skipping layer ", layerId, ": not a caching layer")
skippedLayers.add(layer.id)
continue
}
const flayer: FilteredLayer = new FilteredLayer(layer)
console.log(
"Handling layer ",
layerId,
"which has",
source.features.data.length,
"features"
)
if (source.features.data.length === 0) {
continue
}
const featureProperties: FeaturePropertiesStore = new FeaturePropertiesStore(source)
MetaTagging.addMetatags(
source.features.data,
{
getFeaturesWithin: (_) => {
return <any>[allFeatures.features.data]
},
getFeatureById: getFeatureById,
},
layer,
theme,
osmObjectDownloader,
featureProperties,
{
includeDates: false,
includeNonDates: true,
evaluateStrict: true,
}
)
while (SimpleMetaTaggers.country.runningTasks.size > 0) {
console.log(
"Still waiting for ",
SimpleMetaTaggers.country.runningTasks.size,
" features which don't have a country yet"
)
await ScriptUtils.sleep(250)
}
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
const features = source.features.data
const perBbox = GeoOperations.spreadIntoBboxes(features, targetZoomLevel)
for (let [tileIndex, features] of perBbox) {
const bbox = BBox.fromTileIndex(tileIndex).asGeoJson({})
console.log("Got tile:", tileIndex, layer.id)
if (features.length === 0) {
continue
}
const filteredTile = new FilteringFeatureSource(
flayer,
new StaticFeatureSource(features)
)
console.log(
"Tile " +
layer.id +
"." +
tileIndex +
" contains " +
filteredTile.features.data.length +
" features after filtering (" +
features.length +
") features before"
)
if (filteredTile.features.data.length === 0) {
continue
}
let strictlyCalculated = 0
let featureCount = 0
for (const feature of features) {
// Some cleanup
if (layer.calculatedTags !== undefined) {
// Evaluate all the calculated tags strictly
const calculatedTagKeys = layer.calculatedTags.map((ct) => ct[0])
featureCount++
const props = feature.properties
for (const calculatedTagKey of calculatedTagKeys) {
const strict = props[calculatedTagKey]
if (props.hasOwnProperty(calculatedTagKey)) {
delete props[calculatedTagKey]
}
props[calculatedTagKey] = strict
strictlyCalculated++
if (strictlyCalculated % 100 === 0) {
console.log(
"Strictly calculated ",
strictlyCalculated,
"values for tile",
tileIndex,
": now at ",
featureCount,
"/",
filteredTile.features.data.length,
"examle value: ",
strict
)
}
}
}
delete feature["bbox"]
}
if (clip) {
console.log("Clipping features")
features = [].concat(
...features.map((f: Feature) => GeoOperations.clipWith(<any>f, bbox))
)
}
// Lets save this tile!
const [z, x, y] = Tiles.tile_from_index(tileIndex)
// console.log("Writing tile ", z, x, y, layerId)
const targetPath = geoJsonName(targetdir + "_" + layerId, x, y, z)
createdTiles.push(tileIndex)
// This is the geojson file containing all features for this tile
writeFileSync(
targetPath,
JSON.stringify(
{
type: "FeatureCollection",
features,
},
null,
" "
)
)
console.log("Written tile", targetPath, "with", filteredTile.features.data.length)
}
// All the tiles are written at this point
// Only thing left to do is to create the index
const path = targetdir + "_" + layerId + "_" + targetZoomLevel + "_overview.json"
const perX = {}
createdTiles
.map((i) => Tiles.tile_from_index(i))
.forEach(([z, x, y]) => {
const key = "" + x
if (perX[key] === undefined) {
perX[key] = []
}
perX[key].push(y)
})
console.log("Written overview: ", path, "with ", createdTiles.length, "tiles")
writeFileSync(path, JSON.stringify(perX))
// And, if needed, to create a points-only layer
if (pointsOnlyLayers.indexOf(layer.id) >= 0) {
const filtered = new FilteringFeatureSource(flayer, source)
const features = filtered.features.data
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,
" "
)
)
}
}
const skipped = Array.from(skippedLayers)
if (skipped.length > 0) {
console.warn(
"Did not save any cache files for layers " +
skipped.join(", ") +
" as these didn't set the flag `isOsmCache` to true"
)
}
}
export async function main(args: string[]) {
console.log("Cache builder started with args ", args.join(" "))
ReferencingWaysMetaTagger.enabled = false
if (args.length < 6) {
console.error(
"Expected arguments are: theme zoomlevel targetdirectory lat0 lon0 lat1 lon1 [--generate-point-overview layer-name,layer-name,...] [--force-zoom-level z] [--clip]" +
"--force-zoom-level causes non-cached-layers to be donwnloaded\n" +
"--clip will erase parts of the feature falling outside of the bounding box"
)
return
}
const themeName = args[0]
const zoomlevel = Number(args[1])
console.log(
"Target zoomlevel for the tiles is",
zoomlevel,
"; this can be overridden by the individual layers"
)
const targetdir = args[2] + "/" + themeName
if (!existsSync(args[2])) {
console.log("Directory not found")
throw `The directory ${args[2]} does not exist`
}
const lat0 = Number(args[3])
const lon0 = Number(args[4])
const lat1 = Number(args[5])
const lon1 = Number(args[6])
const clip = args.indexOf("--clip") >= 0
if (isNaN(lat0)) {
throw "The first number (a latitude) is not a valid number"
}
if (isNaN(lon0)) {
throw "The second number (a longitude) is not a valid number"
}
if (isNaN(lat1)) {
throw "The third number (a latitude) is not a valid number"
}
if (isNaN(lon1)) {
throw "The fourth number (a longitude) is not a valid number"
}
const tileRange = Tiles.TileRangeBetween(zoomlevel, lat0, lon0, lat1, lon1)
if (isNaN(tileRange.total)) {
throw "Something has gone wrong: tilerange is NAN"
}
if (tileRange.total === 0) {
console.log("Tilerange has zero tiles - this is probably an error")
return
}
const theme = AllKnownLayouts.allKnownLayouts.get(themeName)
if (theme === undefined) {
const keys = Array.from(AllKnownLayouts.allKnownLayouts.keys())
console.error("The theme " + themeName + " was not found; try one of ", keys)
return
}
theme.layers = theme.layers.filter(
(l) =>
Constants.priviliged_layers.indexOf(<any>l.id) < 0 && !l.id.startsWith("note_import_")
)
console.log("Layers to download:", theme.layers.map((l) => l.id).join(", "))
let generatePointLayersFor = []
if (args[7] == "--generate-point-overview") {
if (args[8] === undefined) {
throw "--generate-point-overview needs a list of layers to generate the overview for (or * for all)"
} else if (args[8] === "*") {
generatePointLayersFor = theme.layers.map((l) => l.id)
} else {
generatePointLayersFor = args[8].split(",")
}
console.log(
"Also generating a point overview for layers ",
generatePointLayersFor.join(",")
)
}
{
const index = args.indexOf("--force-zoom-level")
if (index >= 0) {
const forcedZoomLevel = Number(args[index + 1])
for (const layer of theme.layers) {
layer.source.geojsonSource = "https://127.0.0.1/cache_{layer}_{z}_{x}_{y}.geojson"
layer.source.isOsmCacheLayer = true
layer.source.geojsonZoomLevel = forcedZoomLevel
}
}
}
let failed = 0
do {
try {
const cachingResult = await downloadRaw(targetdir, tileRange, theme)
failed = cachingResult.failed
if (failed > 0) {
await ScriptUtils.sleep(30000)
}
} catch (e) {
console.error(e)
return
}
} while (failed > 0)
const extraFeatures = await downloadExtraData(theme)
const allFeaturesSource = loadAllTiles(targetdir, tileRange, theme, extraFeatures)
await sliceToTiles(allFeaturesSource, theme, targetdir, generatePointLayersFor, clip, zoomlevel)
}
let args = [...process.argv]
if (!args[1]?.endsWith("test/TestAll.ts")) {
args.splice(0, 2)
try {
main(args)
.then(() => console.log("All done!"))
.catch((e) => console.error("Error building cache:", e))
} catch (e) {
console.error("Error building cache:", e)
}
}

View file

@ -1,5 +0,0 @@
#! /bin/bash
# npm run generate:layeroverview
cd ../..
ts-node scripts/generateCache.ts postal_codes 8 /home/pietervdvn/Downloads/postal_codes 49.69606181911566 2.373046875 51.754240074033525 6.459960937499999 --generate-point-overview '*' --force-zoom-level 1

View file

@ -655,6 +655,7 @@
{"properties":{"name":"Greene County Orthoimagery (2022)","id":"Greene_OH_2022","url":"https://gis.greenecountyohio.gov/webgis2/rest/services/Aerials/Aerial_2022_Summer/MapServer/export?f=image&format=jpg&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Greene County, State of Ohio","url":"https://www.greenecountyohio.gov/"},"type":"wms","category":"historicphoto","max_zoom":23},"type":"Feature","geometry":{"coordinates":[[[-84.06204,39.85653],[-84.06161,39.84285],[-84.09735,39.84244],[-84.09652,39.80807],[-84.10545,39.80796],[-84.10369,39.73247],[-84.11258,39.73232],[-84.11066,39.64998],[-84.11956,39.64987],[-84.11781,39.57439],[-84.07344,39.575],[-84.07331,39.56812],[-83.98463,39.56928],[-83.98449,39.56243],[-83.91357,39.56331],[-83.91348,39.55645],[-83.79816,39.55778],[-83.79803,39.55092],[-83.70942,39.55188],[-83.70928,39.54499],[-83.66497,39.54548],[-83.66565,39.58664],[-83.65678,39.58673],[-83.65801,39.66223],[-83.64915,39.66231],[-83.65042,39.73781],[-83.64149,39.73789],[-83.64215,39.77907],[-83.69556,39.7785],[-83.69568,39.7854],[-83.73124,39.78501],[-83.73132,39.79188],[-83.75818,39.79169],[-83.75813,39.7985],[-83.82043,39.79775],[-83.82102,39.82521],[-83.8565,39.82479],[-83.85655,39.83164],[-83.93679,39.83069],[-83.93727,39.85129],[-84.02625,39.85016],[-84.02643,39.85702],[-84.06204,39.85653]]],"type":"Polygon"}},
{"properties":{"name":"Putnam County Orthoimagery (2023)","id":"Licking_OH_2023","url":"https://apps.lickingcounty.gov/arcgis/rest/services/Basemaps/Imagery2023/MapServer/export?f=image&format=jpg&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Licking County, State of Ohio","url":"https://lickingcounty.gov/"},"type":"wms","category":"photo","max_zoom":23},"type":"Feature","geometry":{"coordinates":[[[-82.47752,40.24575],[-82.47607,40.2645],[-82.75072,40.27696],[-82.77362,39.97606],[-82.77941,39.97638],[-82.78238,39.93973],[-82.47333,39.92498],[-82.46314,39.93058],[-82.41846,39.92745],[-82.41897,39.92258],[-82.30121,39.91614],[-82.23414,39.9133],[-82.23164,39.95129],[-82.19885,39.95022],[-82.18284,40.23866],[-82.28095,40.24019],[-82.28094,40.2393],[-82.32857,40.23967],[-82.47752,40.24575]]],"type":"Polygon"}},
{"properties":{"name":"Mercer County Orthoimagery (2021)","id":"Mercer_OH_2021","url":"https://gis.mercercountyohio.org/arcgis/rest/services/aerials/2021Aerials/ImageServer/WMTS/tile/1.0.0/aerials_2021Aerials/default/default028mm/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Mercer County, State of Ohio","url":"https://www.mercercountyohio.org/"},"type":"tms","category":"photo","max_zoom":23},"type":"Feature","geometry":{"coordinates":[[[-84.80424,40.73458],[-84.80284,40.69342],[-84.81186,40.69324],[-84.804,40.46686],[-84.81308,40.46674],[-84.80896,40.34988],[-84.71927,40.35184],[-84.71892,40.34491],[-84.42307,40.35047],[-84.42324,40.35727],[-84.44126,40.35714],[-84.44143,40.36381],[-84.45019,40.36355],[-84.45482,40.50767],[-84.44538,40.50806],[-84.45259,40.73477],[-84.7681,40.72801],[-84.76828,40.73549],[-84.80424,40.73458]]],"type":"Polygon"}},
{"properties":{"name":"Miami County Orthoimagery (2023)","id":"Miami_OH_2023","url":"https://tiles.arcgis.com/tiles/nKtC7LOvnaEyn6u8/arcgis/rest/services/Miami_Aerial_2023/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Miami County, State of Ohio","url":"https://www.co.miami.oh.us/"},"type":"tms","category":"photo","max_zoom":21},"type":"Feature","geometry":{"coordinates":[[[-84.42878,39.91291],[-84.17028,39.91669],[-84.16969,39.88947],[-84.16083,39.88947],[-84.16062,39.88267],[-84.12498,39.88313],[-84.12484,39.87631],[-84.04463,39.87734],[-84.0458,39.92545],[-84.03683,39.92554],[-84.03851,40.00101],[-84.0295,40.00116],[-84.0314,40.08344],[-84.02244,40.08361],[-84.02417,40.15903],[-84.0152,40.15916],[-84.01573,40.18657],[-84.05152,40.18617],[-84.05172,40.19301],[-84.1501,40.19178],[-84.15016,40.1985],[-84.21292,40.19764],[-84.21305,40.20454],[-84.43671,40.20111],[-84.43642,40.18733],[-84.44533,40.18725],[-84.44515,40.18033],[-84.43621,40.18047],[-84.42878,39.91291]]],"type":"Polygon"}},
{"properties":{"name":"Montgomery County Orthoimagery (2022)","id":"Montgomery_OH_2022","url":"https://gis.mcohio.org/arcgis/rest/services/AUDGIS_MrSID/MapServer/export?f=image&format=jpg&layers=show:0&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Montgomery County, State of Ohio","url":"https://www.mcohio.org/"},"type":"wms","category":"photo","max_zoom":21},"type":"Feature","geometry":{"coordinates":[[[-84.48184,39.58592],[-84.36654,39.58782],[-84.3575,39.58113],[-84.35686,39.55709],[-84.34356,39.5573],[-84.33985,39.58481],[-84.25116,39.58618],[-84.10894,39.5745],[-84.10981,39.61224],[-84.07863,39.61276],[-84.07897,39.62969],[-84.09699,39.62962],[-84.09682,39.63286],[-84.10532,39.6328],[-84.10635,39.66049],[-84.10189,39.66042],[-84.10343,39.71531],[-84.09871,39.71537],[-84.09966,39.75313],[-84.09081,39.75313],[-84.0918,39.79436],[-84.08283,39.79452],[-84.08343,39.8151],[-84.08781,39.81506],[-84.08785,39.81839],[-84.09236,39.81846],[-84.09249,39.82864],[-84.08807,39.82868],[-84.08828,39.83563],[-84.07043,39.83593],[-84.07039,39.83237],[-84.0526,39.83263],[-84.05291,39.8464],[-84.04845,39.84647],[-84.04932,39.88417],[-84.07146,39.88379],[-84.12764,39.90026],[-84.16098,39.89976],[-84.16184,39.92725],[-84.49139,39.92218],[-84.48184,39.58592]]],"type":"Polygon"}},
{"properties":{"name":"Ohio Statewide Imagery Program","id":"OSIP","url":"https://geo1.oit.ohio.gov/arcgis/services/OSIP/OSIPIII_MostCurrent/ImageServer/WMSServer?LAYERS=0&STYLES=&CRS={proj}&BBOX={bbox}&FORMAT=image/jpeg&WIDTH={width}&HEIGHT={height}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":false,"text":"Ohio Statewide Imagery Program","url":"https://das.ohio.gov/technology-and-strategy/ogrip/projects/osip"},"type":"wms","category":"photo","min_zoom":8,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-83.1356,41.75081],[-83.13438,41.64959],[-83.44737,41.76038],[-84.82898,41.70637],[-84.84471,39.08477],[-84.74086,39.11164],[-84.64015,39.05545],[-84.49223,39.07255],[-84.34117,38.99922],[-84.22157,38.7812],[-84.08624,38.75421],[-83.9635,38.76403],[-83.74635,38.63385],[-83.65193,38.61172],[-83.54178,38.69283],[-83.46625,38.64614],[-83.39387,38.64368],[-83.30575,38.58466],[-83.14524,38.59942],[-83.00676,38.71002],[-82.91235,38.73212],[-82.8494,38.56006],[-82.73925,38.53545],[-82.57875,38.39745],[-82.29865,38.43198],[-82.26718,38.57236],[-82.16017,38.58712],[-82.16647,38.72475],[-82.19794,38.78856],[-82.12555,38.8278],[-82.11611,38.92336],[-82.02799,38.99922],[-81.9084,38.93315],[-81.95875,38.89397],[-81.9021,38.85477],[-81.73216,38.9258],[-81.72586,39.19461],[-81.54333,39.26288],[-81.515,39.35054],[-81.45521,39.38704],[-81.38912,39.31159],[-81.19399,39.37974],[-80.85095,39.625],[-80.58973,40.2812],[-80.58344,40.49933],[-80.6275,40.59021],[-80.51105,40.62127],[-80.50161,41.99939],[-81.10587,41.84484],[-81.39541,41.7369],[-81.72901,41.52048],[-82.0217,41.53462],[-82.49063,41.40492],[-82.68575,41.50634],[-82.64799,41.62408],[-82.75184,41.63584],[-82.76758,41.73925],[-82.8494,41.7463],[-82.89032,41.69462],[-82.89032,41.54404],[-82.95011,41.54404],[-83.0796,41.62936],[-83.08101,41.7511],[-83.1356,41.75081]]],"type":"Polygon"}},
{"properties":{"name":"Ohio Statewide Imagery Program 1-Foot","id":"OSIP_1ft","url":"https://geo1.oit.ohio.gov/arcgis/services/OSIP/osip_best_avail_1ft/ImageServer/WMSServer?LAYERS=0&STYLES=&CRS={proj}&BBOX={bbox}&FORMAT=image/jpeg&WIDTH={width}&HEIGHT={height}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":false,"text":"Ohio Statewide Imagery Program","url":"https://das.ohio.gov/technology-and-strategy/ogrip/projects/osip"},"type":"wms","category":"photo","min_zoom":8,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-83.1356,41.75081],[-83.13438,41.64959],[-83.44737,41.76038],[-84.82898,41.70637],[-84.84471,39.08477],[-84.74086,39.11164],[-84.64015,39.05545],[-84.49223,39.07255],[-84.34117,38.99922],[-84.22157,38.7812],[-84.08624,38.75421],[-83.9635,38.76403],[-83.74635,38.63385],[-83.65193,38.61172],[-83.54178,38.69283],[-83.46625,38.64614],[-83.39387,38.64368],[-83.30575,38.58466],[-83.14524,38.59942],[-83.00676,38.71002],[-82.91235,38.73212],[-82.8494,38.56006],[-82.73925,38.53545],[-82.57875,38.39745],[-82.29865,38.43198],[-82.26718,38.57236],[-82.16017,38.58712],[-82.16647,38.72475],[-82.19794,38.78856],[-82.12555,38.8278],[-82.11611,38.92336],[-82.02799,38.99922],[-81.9084,38.93315],[-81.95875,38.89397],[-81.9021,38.85477],[-81.73216,38.9258],[-81.72586,39.19461],[-81.54333,39.26288],[-81.515,39.35054],[-81.45521,39.38704],[-81.38912,39.31159],[-81.19399,39.37974],[-80.85095,39.625],[-80.58973,40.2812],[-80.58344,40.49933],[-80.6275,40.59021],[-80.51105,40.62127],[-80.50161,41.99939],[-81.10587,41.84484],[-81.39541,41.7369],[-81.72901,41.52048],[-82.0217,41.53462],[-82.49063,41.40492],[-82.68575,41.50634],[-82.64799,41.62408],[-82.75184,41.63584],[-82.76758,41.73925],[-82.8494,41.7463],[-82.89032,41.69462],[-82.89032,41.54404],[-82.95011,41.54404],[-83.0796,41.62936],[-83.08101,41.7511],[-83.1356,41.75081]]],"type":"Polygon"}},
@ -754,7 +755,7 @@
{"properties":{"name":"Rio Mosaico 2019","id":"rio2019","url":"https://pgeo3.rio.rj.gov.br/arcgis/services/Imagens/Mosaico_2019/ImageServer/WMSServer?FORMAT=image/png&TRANSPARENT=TRUE&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=0&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"text":"Instituto Pereira Passos - Prefeitura da Cidade do Rio de Janeiro","url":"https://www.rio.rj.gov.br/web/ipp"},"type":"wms","category":"photo","min_zoom":4,"best":true},"type":"Feature","geometry":{"coordinates":[[[-43.59375,-23.09163],[-43.49213,-23.09163],[-43.49043,-23.05158],[-43.42965,-23.04994],[-43.42958,-23.03186],[-43.42655,-23.02912],[-43.35517,-23.02925],[-43.35275,-23.0322],[-43.35273,-23.04931],[-43.32336,-23.05052],[-43.32046,-23.07079],[-43.27378,-23.07079],[-43.27156,-23.05116],[-43.24219,-23.04994],[-43.24219,-23.02902],[-43.23053,-23.02917],[-43.22783,-23.03186],[-43.22776,-23.09163],[-43.11654,-23.09163],[-43.11722,-23.03416],[-43.14502,-23.03414],[-43.1481,-23.03106],[-43.14754,-22.98957],[-43.11653,-22.98732],[-43.11722,-22.90906],[-43.14502,-22.90904],[-43.14805,-22.90629],[-43.14923,-22.86457],[-43.14741,-22.82414],[-43.14433,-22.82106],[-43.08564,-22.82104],[-43.08564,-22.72164],[-43.16528,-22.72227],[-43.1653,-22.73907],[-43.16696,-22.7417],[-43.19618,-22.74253],[-43.1962,-22.76033],[-43.19771,-22.76284],[-43.25866,-22.76343],[-43.25873,-22.78092],[-43.26176,-22.78367],[-43.41453,-22.78622],[-43.41511,-22.80369],[-43.42776,-22.80537],[-43.45873,-22.80481],[-43.46052,-22.80212],[-43.46123,-22.78622],[-43.55392,-22.78749],[-43.5541,-22.79474],[-43.55793,-22.8019],[-43.5704,-22.80269],[-43.57864,-22.81028],[-43.58017,-22.81603],[-43.59306,-22.81851],[-43.59375,-22.84646],[-43.72215,-22.84891],[-43.73108,-22.85144],[-43.73374,-22.85711],[-43.75373,-22.85651],[-43.75466,-22.86261],[-43.75861,-22.86767],[-43.78738,-22.87297],[-43.78944,-22.87424],[-43.78951,-22.8854],[-43.79156,-22.8879],[-43.8121,-22.88817],[-43.8121,-22.93182],[-43.80039,-22.93188],[-43.78897,-22.93578],[-43.78326,-22.94066],[-43.77767,-22.94095],[-43.76014,-22.95055],[-43.75786,-22.98804],[-43.72977,-22.98767],[-43.72766,-22.99051],[-43.72696,-23.00888],[-43.69775,-23.00989],[-43.69606,-23.04994],[-43.68379,-23.0512],[-43.68033,-23.06129],[-43.68026,-23.07079],[-43.60544,-23.07086],[-43.60269,-23.07389],[-43.60267,-23.091],[-43.59375,-23.09163]]],"type":"Polygon"}},
{"properties":{"name":"Rio Mosaico 2022","id":"rio2022","url":"https://pgeo3.rio.rj.gov.br/arcgis/services/Imagens/Mosaico_2022/ImageServer/WMSServer?FORMAT=image/png&TRANSPARENT=TRUE&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=0&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"text":"Instituto Pereira Passos - Prefeitura da Cidade do Rio de Janeiro","url":"https://www.rio.rj.gov.br/web/ipp"},"type":"wms","category":"photo","min_zoom":4},"type":"Feature","geometry":{"coordinates":[[[-43.59375,-23.09163],[-43.49213,-23.09163],[-43.49043,-23.05158],[-43.42965,-23.04994],[-43.42958,-23.03186],[-43.42655,-23.02912],[-43.35517,-23.02925],[-43.35275,-23.0322],[-43.35273,-23.04931],[-43.32336,-23.05052],[-43.32046,-23.07079],[-43.27378,-23.07079],[-43.27156,-23.05116],[-43.24219,-23.04994],[-43.24219,-23.02902],[-43.23053,-23.02917],[-43.22783,-23.03186],[-43.22776,-23.09163],[-43.11654,-23.09163],[-43.11722,-23.03416],[-43.14502,-23.03414],[-43.1481,-23.03106],[-43.14754,-22.98957],[-43.11653,-22.98732],[-43.11722,-22.90906],[-43.14502,-22.90904],[-43.14805,-22.90629],[-43.14923,-22.86457],[-43.14741,-22.82414],[-43.14433,-22.82106],[-43.08564,-22.82104],[-43.08564,-22.72164],[-43.16528,-22.72227],[-43.1653,-22.73907],[-43.16696,-22.7417],[-43.19618,-22.74253],[-43.1962,-22.76033],[-43.19771,-22.76284],[-43.25866,-22.76343],[-43.25873,-22.78092],[-43.26176,-22.78367],[-43.41453,-22.78622],[-43.41511,-22.80369],[-43.42776,-22.80537],[-43.45873,-22.80481],[-43.46052,-22.80212],[-43.46123,-22.78622],[-43.55392,-22.78749],[-43.5541,-22.79474],[-43.55793,-22.8019],[-43.5704,-22.80269],[-43.57864,-22.81028],[-43.58017,-22.81603],[-43.59306,-22.81851],[-43.59375,-22.84646],[-43.72215,-22.84891],[-43.73108,-22.85144],[-43.73374,-22.85711],[-43.75373,-22.85651],[-43.75466,-22.86261],[-43.75861,-22.86767],[-43.78738,-22.87297],[-43.78944,-22.87424],[-43.78951,-22.8854],[-43.79156,-22.8879],[-43.8121,-22.88817],[-43.8121,-22.93182],[-43.80039,-22.93188],[-43.78897,-22.93578],[-43.78326,-22.94066],[-43.77767,-22.94095],[-43.76014,-22.95055],[-43.75786,-22.98804],[-43.72977,-22.98767],[-43.72766,-22.99051],[-43.72696,-23.00888],[-43.69775,-23.00989],[-43.69606,-23.04994],[-43.68379,-23.0512],[-43.68033,-23.06129],[-43.68026,-23.07079],[-43.60544,-23.07086],[-43.60269,-23.07389],[-43.60267,-23.091],[-43.59375,-23.09163]]],"type":"Polygon"}},
{"properties":{"name":"Jaraguá do Sul Ortomosaico 2020","id":"jaragua-do-sul-2020","url":"https://www.jaraguadosul.sc.gov.br/geo/ortomosaico2020/{zoom}/{x}/{y}.png","attribution":{"text":"Prefeitura de Jaraguá do Sul, SC","url":"https://sistemas.jaraguadosul.sc.gov.br/index.php?class=GeoWelcomeView"},"type":"tms","category":"photo","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-49.25368,-26.26563],[-49.17549,-26.31065],[-49.16931,-26.35804],[-49.19403,-26.38449],[-49.19266,-26.42016],[-49.21051,-26.43676],[-49.21806,-26.47733],[-49.22562,-26.48471],[-49.24621,-26.48901],[-49.29634,-26.54185],[-49.30595,-26.58054],[-49.28106,-26.61953],[-49.23798,-26.61922],[-49.20433,-26.62966],[-49.17824,-26.61615],[-49.1645,-26.65237],[-49.13292,-26.64316],[-49.10408,-26.61063],[-49.10133,-26.58177],[-49.0876,-26.57993],[-49.08554,-26.55168],[-49.0567,-26.54615],[-49.05121,-26.51912],[-49.03404,-26.52219],[-49.01756,-26.51298],[-49.01138,-26.48287],[-49.02511,-26.45643],[-49.09515,-26.39863],[-49.10545,-26.39371],[-49.10477,-26.36972],[-49.13635,-26.33219],[-49.13841,-26.30265],[-49.16725,-26.26571],[-49.16725,-26.21336],[-49.19128,-26.21274],[-49.23454,-26.23061],[-49.23386,-26.25524],[-49.25368,-26.26563]]],"type":"Polygon"}},
{"properties":{"name":"Bing Maps Aerial","id":"Bing","url":"https://ecn.t1.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=14296&pr=odbl&n=f","type":"bing","category":"photo","min_zoom":1,"max_zoom":22},"type":"Feature","geometry":null},
{"properties":{"name":"Bing Maps Aerial","id":"Bing","url":"https://ecn.t1.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=14307&pr=odbl&n=f","type":"bing","category":"photo","min_zoom":1,"max_zoom":22},"type":"Feature","geometry":null},
{"properties":{"name":"CyclOSM","id":"cyclosm","url":"https://{switch:a,b,c}.tile-cyclosm.openstreetmap.fr/cyclosm/{zoom}/{x}/{y}.png","attribution":{"text":"Rendering: CyclOSM (hosted by OpenStreetMap France) © Map data OpenStreetMap contributors","url":"https://www.cyclosm.org/"},"type":"tms","category":"osmbasedmap","max_zoom":20},"type":"Feature","geometry":null},
{"properties":{"name":"Esri World Imagery","id":"EsriWorldImagery","url":"https://{switch:services,server}.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":true,"text":"Terms & Feedback","url":"https://wiki.openstreetmap.org/wiki/Esri"},"type":"tms","category":"photo","max_zoom":22,"default":true},"type":"Feature","geometry":null},
{"properties":{"name":"Esri World Imagery (Clarity) Beta","id":"EsriWorldImageryClarity","url":"https://clarity.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":true,"text":"Terms & Feedback","url":"https://wiki.openstreetmap.org/wiki/Esri"},"type":"tms","category":"photo","max_zoom":22,"default":true},"type":"Feature","geometry":null},

File diff suppressed because it is too large Load diff