From 65a941401b9e3f90b166825ee2f68d409f238c7a Mon Sep 17 00:00:00 2001 From: Foxino Date: Thu, 26 Oct 2023 11:07:32 +0100 Subject: [PATCH] OSM Trees --- .../components/osm_land_use_component.ts | 234 +++++++++++++----- 1 file changed, 170 insertions(+), 64 deletions(-) diff --git a/app/javascript/projects/modelling/components/osm_land_use_component.ts b/app/javascript/projects/modelling/components/osm_land_use_component.ts index 08e93d3b..4de7ef43 100644 --- a/app/javascript/projects/modelling/components/osm_land_use_component.ts +++ b/app/javascript/projects/modelling/components/osm_land_use_component.ts @@ -8,9 +8,9 @@ import { PreviewControl } from "../controls/preview" import { Extent } from "ol/extent" import { createXYZ } from "ol/tilegrid" import * as proj4 from "proj4" -import { Polygon } from "ol/geom" +import { Point, Polygon } from "ol/geom" -import { currentExtent as extent, zoomLevel } from "../bounding_box" +import { currentExtent, currentExtent as extent, zoomLevel } from "../bounding_box" @@ -18,6 +18,8 @@ interface LandUseCategory { code: string name: string id: number + type: "node" | "way" | "relation" + zoom: number } interface OverpassCoord { @@ -31,16 +33,18 @@ interface OverpassFeature { bounds: object tags: object geometry: Array + lat: number + lon: number } -export async function retrieveLandUseData(extent: Extent, landuse: string): Promise { +export async function retrieveLandUseData(extent: Extent, landuse: string, type: "node" | "way" | "relation"): Promise { const [e0, e1] = [(proj4 as any).default('EPSG:3857', 'EPSG:4326', extent.slice(0, 2)), (proj4 as any).default('EPSG:3857', 'EPSG:4326', extent.slice(2, 4))] const query = ` [out:json]; - way(${e0[1]}, ${e0[0]}, ${e1[1]}, ${e1[0]})${landuse}; + ${type}(${e0[1]}, ${e0[0]}, ${e1[1]}, ${e1[0]})${landuse}; out geom; `; @@ -60,192 +64,275 @@ export const LandUseCategories: LandUseCategory[] = [ { "code": "[aeroway=aerodrome]", "name": "Aerodrome", - "id": 0 + "id": 0, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=allotments]", "name": "Allotments", - "id": 1 + "id": 1, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=brownfield]", "name": "Brownfield", - "id": 2 + "id": 2, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=cemetery]", "name": "Cemetery", - "id": 3 + "id": 3, + "type": "way", + "zoom": zoomLevel }, { "code": "[amenity=college]", "name": "College", - "id": 4 + "id": 4, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=commercial]", "name": "Commerical", - "id": 5 + "id": 5, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=construction]", "name": "Construction", - "id": 6 + "id": 6, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=farmland]", "name": "Farm Land", - "id": 7 + "id": 7, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=farmyard]", "name": "Farm Yard", - "id": 8 + "id": 8, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=forest]", "name": "Forest", - "id": 9 + "id": 9, + "type": "way", + "zoom": zoomLevel }, { "code": "[leisure=garden]", "name": "Garden", - "id": 10 + "id": 10, + "type": "way", + "zoom": zoomLevel }, { "code": "[leisure=garages]", "name": "Garages", - "id": 11 + "id": 11, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=meadow]", "name": "Grazing", - "id": 12 + "id": 12, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=greenfield]", "name": "Greenfield", - "id": 13 + "id": 13, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=greenhouse_horticulture]", "name": "Greenhouses", - "id": 14 + "id": 14, + "type": "way", + "zoom": zoomLevel }, { "code": "[amenity=hospital]", "name": "Hospital", - "id": 15 + "id": 15, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=industrial]", "name": "Industrial", - "id": 16 + "id": 16, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=landfill]", "name": "Landfill", - "id": 17 + "id": 17, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=meadow]", "name": "Meadow / Pasture", - "id": 18 + "id": 18, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=military]", "name": "Military", - "id": 19 + "id": 19, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=quarry]", "name": "Mine (open pit) / Quarry", - "id": 20 + "id": 20, + "type": "way", + "zoom": zoomLevel }, { "code": "[boundary=national_park]", "name": "National Park", - "id": 21 + "id": 21, + "type": "way", + "zoom": zoomLevel }, { "code": "[boundary=nature_reserve]", "name": "Nature Reserve", - "id": 22 + "id": 22, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=orchard]", "name": "Orchard / Plantation", - "id": 23 + "id": 23, + "type": "way", + "zoom": zoomLevel }, { "code": "[leisure=park]", "name": "Park", - "id": 24 + "id": 24, + "type": "way", + "zoom": zoomLevel }, { "code": "[amenity=parking]", "name": "Parking", - "id": 25 + "id": 25, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=plant_nursery]", "name": "Plant Nursery", - "id": 26 + "id": 26, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=railway]", "name": "Railway", - "id": 27 + "id": 27, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=recreation_ground]", "name": "Recreation Ground", - "id": 28 + "id": 28, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=religious]", "name": "Religious", - "id": 29 + "id": 29, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=residential]", "name": "Residential", - "id": 30 + "id": 30, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=retail]", "name": "Retail", - "id": 31 + "id": 31, + "type": "way", + "zoom": zoomLevel }, { "code": "[amenity=school]", "name": "School", - "id": 32 + "id": 32, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=salt_pond]", "name": "Salt Pond", - "id": 33 + "id": 33, + "type": "way", + "zoom": zoomLevel }, { "code": "[amenity=university]", "name": "University", - "id": 34 + "id": 34, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=village_green]", "name": "Village Green", - "id": 35 + "id": 35, + "type": "way", + "zoom": zoomLevel }, { "code": "[landuse=vineyard]", "name": "Vineyard", - "id": 36 + "id": 36, + "type": "way", + "zoom": zoomLevel }, { "code": "[man_made=wastewater_plant]", "name": "Waste water treatment", - "id": 37 + "id": 37, + "type": "way", + "zoom": zoomLevel + }, + { + "code": "[natural=tree]", + "name": "Tree", + "id": 38, + "type": "node", + "zoom": 23 }, ] @@ -288,8 +375,6 @@ export class OSMLandUseComponent extends BaseComponent { async worker(node: NodeData, inputs: WorkerInputs, outputs: WorkerOutputs, ...args: unknown[]) { - const zoom = zoomLevel - const editorNode = this.editor?.nodes.find(n => n.id === node.id) if (editorNode === undefined) { return } @@ -299,12 +384,14 @@ export class OSMLandUseComponent extends BaseComponent { const code = LandUseCategories[index].code const name = LandUseCategories[index].name + const type = LandUseCategories[index].type + const zoom = LandUseCategories[index].zoom if (this.outputCache.has(code)) { const result = editorNode.meta.output = outputs['out'] = this.outputCache.get(code) } else { - const json = await retrieveLandUseData(extent, code) + const json = await retrieveLandUseData(extent, code, type) const features = json.elements as Array @@ -321,35 +408,54 @@ export class OSMLandUseComponent extends BaseComponent { ) result.name = name + features.forEach((feature) => { - const geom = feature.geometry + if (feature.type === "node") { + + const [x, y] = (proj4 as any).default('EPSG:4326', 'EPSG:3857', [feature.lon, feature.lat]) + + const p = new Point([x, y]) - const epsg3857_geometry = geom.map((e) => (proj4 as any).default('EPSG:4326', 'EPSG:3857', [e.lon, e.lat])) + const featureTileRange = tileGrid.getTileRangeForExtentAndZ( + p.getExtent(), + zoom + ) - const polygon = new Polygon([epsg3857_geometry]) + result.set(featureTileRange.maxX, featureTileRange.minY, true) - const featureTileRange = tileGrid.getTileRangeForExtentAndZ( - polygon.getExtent(), - zoom - ) - for ( - let x = Math.max(featureTileRange.minX, outputTileRange.minX); - x <= Math.min(featureTileRange.maxX, outputTileRange.maxX); - ++x - ) { + } else { + + const geom = feature.geometry + + const epsg3857_geometry = geom.map((e) => (proj4 as any).default('EPSG:4326', 'EPSG:3857', [e.lon, e.lat])) + + const polygon = new Polygon([epsg3857_geometry]) + + const featureTileRange = tileGrid.getTileRangeForExtentAndZ( + polygon.getExtent(), + zoom + ) + for ( - let y = Math.max(featureTileRange.minY, outputTileRange.minY); - y <= Math.min(featureTileRange.maxY, outputTileRange.maxY); - ++y + let x = Math.max(featureTileRange.minX, outputTileRange.minX); + x <= Math.min(featureTileRange.maxX, outputTileRange.maxX); + ++x ) { - const tileExtent = tileGrid.getTileCoordExtent([zoom, x, y]) - if (polygon.intersectsExtent(tileExtent)) { - - result.set(x, y, true) + for ( + let y = Math.max(featureTileRange.minY, outputTileRange.minY); + y <= Math.min(featureTileRange.maxY, outputTileRange.maxY); + ++y + ) { + const tileExtent = tileGrid.getTileCoordExtent([zoom, x, y]) + if (polygon.intersectsExtent(tileExtent)) { + console.log(x, y) + result.set(x, y, true) + } } } + }