Scripts(community_index): create script to update community index files, create weekly data maintenance script

This commit is contained in:
Pieter Vander Vennet 2025-01-27 02:32:19 +01:00
parent 36a9b49c66
commit 7bddaa7d4c
11 changed files with 412 additions and 141 deletions

View file

@ -0,0 +1,31 @@
name: Weekly data updates
on:
schedule:
-cron: "* * * * 1"
jobs:
deploy_on_hetzner_single:
runs-on: [ ubuntu-latest, hetzner-access ]
runs-on: [ ubuntu-latest, hetzner-access ]
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: "20"
cache: "npm"
cache-dependency-path: package-lock.json
- name: install deps
run: npm ci
shell: bash
- name: update community index files
shell: bash
run: |
mkdir community-index
npm run download:community-index -- community-index/
zip community-index.zip community-index/*
scp community-index.zip hetzner:data/

View file

@ -119,6 +119,7 @@
"download:editor-layer-index": "vite-node scripts/downloadEli.ts",
"download:stats": "vite-node scripts/GenerateSeries.ts",
"download:images": "vite-node scripts/generateImageAnalysis.ts -- ~/data/imgur-image-backup/",
"download:community-index": "vite-node scripts/downloadCommunityIndex.ts -- /tmp/test",
"weblate:add-upstream": "git remote add weblate https://translate.mapcomplete.org/git/mapcomplete/core/ ; git remote update weblate",
"weblate:fix": "npm run weblate:add-upstream && git merge weblate/master && git rebase origin/master && git push origin master",
"lint": "npm run lint:prettier && npm run lint:eslint && npm run lint:themes",

View file

@ -0,0 +1,116 @@
import Script from "./Script"
import { CommunityResource } from "../src/Logic/Web/CommunityIndex"
import { Utils } from "../src/Utils"
import { FeatureCollection, MultiPolygon, Polygon } from "geojson"
import { writeFileSync } from "fs"
import { GeoOperations } from "../src/Logic/GeoOperations"
import { Tiles } from "../src/Models/TileRange"
import ScriptUtils from "./ScriptUtils"
class DownloadCommunityIndex extends Script {
constructor() {
super("Updates the community index")
}
printHelp() {
console.log("Arguments are:\noutputdirectory")
}
private static targetZoomlevel: number = 6
private static upstreamUrl: string = "https://raw.githubusercontent.com/osmlab/osm-community-index/main/dist/"
/**
* Prunes away unnecessary fields from a CommunityResource
* @private
*/
private static stripResource(r: Readonly<CommunityResource>): CommunityResource {
return {
id: r.id,
languageCodes: r.languageCodes,
account: r.account,
type: r.type,
resolved: {
name: r.resolved.name,
description: r.resolved.description,
url: r.resolved.url
}
}
}
private static stripResourcesObj(resources: Readonly<Record<string, Readonly<CommunityResource>>>) {
const stripped: Record<string, CommunityResource> = {}
for (const k in resources) {
stripped[k] = DownloadCommunityIndex.stripResource(resources[k])
}
return stripped
}
public static async update(targetDirectory: string) {
const data = await Utils.downloadJson<FeatureCollection<Polygon | MultiPolygon, {
resources: Record<string, CommunityResource>,
nameEn: string,
id: string
}>>(DownloadCommunityIndex.upstreamUrl + "completeFeatureCollection.json"
)
const features = data.features
const global = features.find(
f => f.id === "Q2"
)
const globalProperties = DownloadCommunityIndex.stripResourcesObj(global.properties.resources)
writeFileSync(targetDirectory + "/global.json", JSON.stringify(globalProperties), "utf8")
console.log("Written global properties")
const types = new Set<string>()
for (const f of features) {
const res = f.properties.resources
for (const k in res) {
types.add(res[k].type)
}
}
for (const type of types) {
const url = `${DownloadCommunityIndex.upstreamUrl}img/${type}.svg`
await ScriptUtils.DownloadFileTo(url, `${targetDirectory}/${type}.svg`)
}
const local = features.filter(f => f.id !== "Q2")
const spread = GeoOperations.spreadIntoBboxes(local, DownloadCommunityIndex.targetZoomlevel)
let written = 0
let skipped = 0
writeFileSync(targetDirectory + "local.geojson", JSON.stringify({ type: "FeatureCollection", features: local }))
for (const tileIndex of spread.keys()) {
const features = spread.get(tileIndex)
const clipped = GeoOperations.clipAllInBox(features, tileIndex)
if (clipped.length === 0) {
skipped++
features.push(Tiles.asGeojson(tileIndex))
writeFileSync(`${targetDirectory + tileIndex}_skipped.geojson`, JSON.stringify({
type: "FeatureCollection", features
}))
continue
}
const [z, x, y] = Tiles.tile_from_index(tileIndex)
const path = `${targetDirectory}/tile_${z}_${x}_${y}.geojson`
clipped.forEach((f) => {
delete f.bbox
})
writeFileSync(path, JSON.stringify({ type: "FeatureCollection", features: clipped }), "utf8")
written++
console.log(`Written tile ${path}`)
}
console.log(`Created ${written} tiles, skipped ${skipped}`)
}
async main(args: string[]): Promise<void> {
const path = args[0]
if (!path) {
this.printHelp()
return
}
await DownloadCommunityIndex.update(path)
}
}
new DownloadCommunityIndex().run()

View file

@ -1,13 +1,12 @@
import * as fs from "fs"
import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"
import * as readline from "readline"
import ScriptUtils from "./ScriptUtils"
import { Utils } from "../Utils"
import Script from "./Script"
import { BBox } from "../Logic/BBox"
import { GeoOperations } from "../Logic/GeoOperations"
import { Tiles } from "../Models/TileRange"
import { Feature } from "geojson"
import { features } from "monaco-editor/esm/metadata"
/**
* This script slices a big newline-delimeted geojson file into tiled geojson
@ -96,34 +95,15 @@ class Slice extends Script {
features: Feature[],
tileIndex: number,
outputDirectory: string,
doSlice: boolean,
doClip: boolean,
handled: number,
maxNumberOfTiles: number
) {
): boolean {
if (doClip) {
features = GeoOperations.clipAllInBox(features, tileIndex)
}
const [z, x, y] = Tiles.tile_from_index(tileIndex)
const path = `${outputDirectory}/tile_${z}_${x}_${y}.geojson`
const box = BBox.fromTileIndex(tileIndex)
if (doSlice) {
features = Utils.NoNull(
features.map((f) => {
const bbox = box.asGeoJson({})
const properties = {
...f.properties,
id: (f.properties?.id ?? "") + "_" + z + "_" + x + "_" + y,
}
if (GeoOperations.completelyWithin(bbox, <any>f)) {
bbox.properties = properties
return bbox
}
const intersection = GeoOperations.intersect(f, box.asGeoJson({}))
if (intersection) {
intersection.properties = properties
}
return intersection
})
)
}
features.forEach((f) => {
delete f.bbox
})
@ -177,7 +157,7 @@ class Slice extends Script {
}
console.log("Using directory ", outputDirectory)
let allFeatures: any[]
let allFeatures: Feature[]
if (inputFile.endsWith(".geojson")) {
console.log("Detected geojson")
allFeatures = await this.readFeaturesFromGeoJson(inputFile)
@ -202,18 +182,16 @@ class Slice extends Script {
}
const maxNumberOfTiles = Math.pow(2, zoomlevel) * Math.pow(2, zoomlevel)
let handled = 0
StaticFeatureSource.fromGeojson(allFeatures).features.addCallbackAndRun((feats) => {
GeoOperations.slice(zoomlevel, feats).forEach((tileData, tileIndex) => {
handled = handled + 1
this.handleTileData(
tileData,
tileIndex,
outputDirectory,
doSlice,
handled,
maxNumberOfTiles
)
})
GeoOperations.slice(zoomlevel, features).forEach((tileData, tileIndex) => {
handled = handled + 1
this.handleTileData(
tileData,
tileIndex,
outputDirectory,
doSlice,
handled,
maxNumberOfTiles
)
})
}
}

View file

@ -75,7 +75,7 @@ export default class SaveFeatureSourceToLocalStorage {
this.storage = storage
const singleTileSavers: Map<number, SingleTileSaver> = new Map<number, SingleTileSaver>()
features.features.addCallbackAndRunD((features) => {
const sliced = GeoOperations.slice(zoomlevel, features)
const sliced = GeoOperations.spreadIntoBboxes(features, zoomlevel)
sliced.forEach((features, tileIndex) => {
let tileSaver = singleTileSavers.get(tileIndex)

View file

@ -1,6 +1,6 @@
import { BBox } from "./BBox"
import * as turf from "@turf/turf"
import { AllGeoJSON, booleanWithin, Coord } from "@turf/turf"
import { AllGeoJSON, booleanWithin, Coord, Polygon } from "@turf/turf"
import {
Feature,
FeatureCollection,
@ -9,13 +9,13 @@ import {
MultiLineString,
MultiPolygon,
Point,
Polygon,
Position,
Position
} from "geojson"
import { Tiles } from "../Models/TileRange"
import { Utils } from "../Utils"
import { NearestPointOnLine } from "@turf/nearest-point-on-line"
;("use strict")
("use strict")
export class GeoOperations {
private static readonly _earthRadius = 6378137
@ -29,7 +29,7 @@ export class GeoOperations {
"behind",
"sharp_left",
"left",
"slight_left",
"slight_left"
] as const
private static reverseBearing = {
N: 0,
@ -47,7 +47,7 @@ export class GeoOperations {
W: 270,
WNW: 292.5,
NW: 315,
NNW: 337.5,
NNW: 337.5
}
/**
@ -61,8 +61,8 @@ export class GeoOperations {
}
public static intersect(
f0: Feature<Polygon | MultiPolygon>,
f1: Feature<Polygon | MultiPolygon>
f0: Readonly<Feature<Polygon | MultiPolygon>>,
f1: Readonly<Feature<Polygon | MultiPolygon>>
): Feature<Polygon | MultiPolygon> | null {
return turf.intersect(f0, f1)
}
@ -309,7 +309,7 @@ export class GeoOperations {
bufferSizeInMeter: number
): Feature<Polygon | MultiPolygon> | FeatureCollection<Polygon | MultiPolygon> {
return turf.buffer(feature, bufferSizeInMeter / 1000, {
units: "kilometers",
units: "kilometers"
})
}
@ -325,9 +325,9 @@ export class GeoOperations {
[lon0, lat],
[lon0, lat0],
[lon, lat0],
[lon, lat],
],
},
[lon, lat]
]
}
}
}
@ -368,9 +368,9 @@ export class GeoOperations {
type: "Feature",
geometry: {
type: "LineString",
coordinates: way.geometry.coordinates[0],
coordinates: way.geometry.coordinates[0]
},
properties: way.properties,
properties: way.properties
}
}
if (way.geometry.type === "MultiPolygon") {
@ -378,9 +378,9 @@ export class GeoOperations {
type: "Feature",
geometry: {
type: "MultiLineString",
coordinates: way.geometry.coordinates[0],
coordinates: way.geometry.coordinates[0]
},
properties: way.properties,
properties: way.properties
}
}
if (way.geometry.type === "LineString") {
@ -512,6 +512,8 @@ export class GeoOperations {
/**
* Given a list of features, will construct a map of slippy map tile-indices.
* Features of which the BBOX overlaps with the corresponding slippy map tile are added to the corresponding array
*
* Also @see clipAllInBox
* @param features
* @param zoomlevel
*/
@ -535,6 +537,33 @@ export class GeoOperations {
return perBbox
}
/**
* Given a list of features, returns a new list of features so that the features are clipped into the given tile-index.
* Note: IDs are rewritten
* Also @see spreadIntoBBoxes
*/
public static clipAllInBox(features: ReadonlyArray<Readonly<Feature>>, tileIndex: number): Feature[] {
const bbox = Tiles.asGeojson(tileIndex)
const newFeatures: Feature[] = []
for (const f of features) {
const intersectionParts = GeoOperations.clipWith(f, bbox)
for (let i = 0; i < intersectionParts.length; i++) {
const intersectionPart = intersectionParts[i]
let id = (f.properties?.id ?? "") + "_" + tileIndex
if (i > 0) {
id += "_part_" + i
}
const properties = {
...f.properties,
id
}
intersectionPart.properties = properties
newFeatures.push(intersectionPart)
}
}
return Utils.NoNull(newFeatures)
}
public static toGpx(
locations:
| Feature<LineString>
@ -558,8 +587,8 @@ export class GeoOperations {
properties: {},
geometry: {
type: "Point",
coordinates: p,
},
coordinates: p
}
}
)
}
@ -575,7 +604,7 @@ export class GeoOperations {
trackPoints.push(trkpt)
}
const header =
'<gpx version="1.1" creator="mapcomplete.org" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">'
"<gpx version=\"1.1\" creator=\"mapcomplete.org\" xmlns=\"http://www.topografix.com/GPX/1/1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">"
return (
header +
"\n<name>" +
@ -614,7 +643,7 @@ export class GeoOperations {
trackPoints.push(trkpt)
}
const header =
'<gpx version="1.1" creator="mapcomplete.org" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">'
"<gpx version=\"1.1\" creator=\"mapcomplete.org\" xmlns=\"http://www.topografix.com/GPX/1/1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">"
return (
header +
"\n<name>" +
@ -640,7 +669,7 @@ export class GeoOperations {
const copy = {
...feature,
geometry: { ...feature.geometry },
geometry: { ...feature.geometry }
}
let coordinates: [number, number][]
if (feature.geometry.type === "LineString") {
@ -698,8 +727,8 @@ export class GeoOperations {
type: "Feature",
geometry: {
type: "LineString",
coordinates: [a, b],
},
coordinates: [a, b]
}
},
distanceMeter,
{ units: "meters" }
@ -736,17 +765,26 @@ export class GeoOperations {
* GeoOperations.completelyWithin(park, pond) // => false
*/
static completelyWithin(
feature: Feature,
possiblyEnclosingFeature: Feature<Polygon | MultiPolygon>
feature: Readonly<Feature>,
possiblyEnclosingFeature: Readonly<Feature<Polygon | MultiPolygon>>
): boolean {
if (feature.geometry.type === "MultiPolygon") {
const polygons = feature.geometry.coordinates.map(coordinates =>
<Feature<Polygon>>{
type: "Feature", geometry: {
type: "Polygon", coordinates
}
})
return !polygons.some(polygon => !booleanWithin(polygon, possiblyEnclosingFeature))
}
return booleanWithin(feature, possiblyEnclosingFeature)
}
/**
* Create an intersection between two features.
* One or multiple new feature is returned based on 'toSplit', which'll have a geometry that is completely withing boundary
* One or multiple new feature are returned based on 'toSplit', which'll have a geometry that is completely withing boundary
*/
public static clipWith(toSplit: Feature, boundary: Feature<Polygon>): Feature[] {
public static clipWith(toSplit: Readonly<Feature>, boundary: Readonly<Feature<Polygon>>): Feature[] {
if (toSplit.geometry.type === "Point") {
const p = <Feature<Point>>toSplit
if (GeoOperations.inside(<[number, number]>p.geometry.coordinates, boundary)) {
@ -757,9 +795,9 @@ export class GeoOperations {
}
if (toSplit.geometry.type === "LineString") {
const splitup = turf.lineSplit(<Feature<LineString>>toSplit, boundary)
const kept = []
for (const f of splitup.features) {
const splitup: Feature<LineString>[] = turf.lineSplit(<Feature<LineString>>toSplit, boundary).features
const kept: Feature[] = []
for (const f of splitup) {
if (!GeoOperations.inside(GeoOperations.centerpointCoordinates(f), boundary)) {
continue
}
@ -787,7 +825,24 @@ export class GeoOperations {
return kept
}
if (toSplit.geometry.type === "Polygon" || toSplit.geometry.type == "MultiPolygon") {
const splitup = turf.intersect(<Feature<Polygon>>toSplit, boundary)
if (splitup === null) {
// No intersection found.
// Either: the boundary is contained fully in 'toSplit', 'toSplit' is contained fully in 'boundary' or they are unrelated at all
if (GeoOperations.completelyWithin(toSplit, boundary)) {
return [toSplit]
}
if (GeoOperations.completelyWithin(boundary, <Feature<Polygon | MultiPolygon>>toSplit)) {
return [{
type: "Feature",
properties: { ...toSplit.properties },
geometry: boundary.geometry,
bbox: boundary.bbox
}]
}
return []
}
splitup.properties = { ...toSplit.properties }
return [splitup]
}
@ -864,32 +919,6 @@ export class GeoOperations {
}
}
/**
* Constructs all tiles where features overlap with and puts those features in them.
* Long features (e.g. lines or polygons) which overlap with multiple tiles are referenced in each tile they overlap with
* @param zoomlevel
* @param features
*/
public static slice(zoomlevel: number, features: Feature[]): Map<number, Feature[]> {
const tiles = new Map<number, Feature[]>()
for (const feature of features) {
const bbox = BBox.get(feature)
Tiles.MapRange(Tiles.tileRangeFrom(bbox, zoomlevel), (x, y) => {
const i = Tiles.tile_index(zoomlevel, x, y)
let tiledata = tiles.get(i)
if (tiledata === undefined) {
tiledata = []
tiles.set(i, tiledata)
}
tiledata.push(feature)
})
}
return tiles
}
/**
* Creates a linestring object based on the outer ring of the given polygon
*
@ -905,8 +934,8 @@ export class GeoOperations {
properties: p.properties,
geometry: {
type: "LineString",
coordinates: p.geometry.coordinates[0],
},
coordinates: p.geometry.coordinates[0]
}
}
}
@ -934,7 +963,7 @@ export class GeoOperations {
console.debug("SPlitting way", feature.properties.id)
result.push({
...feature,
geometry: { ...feature.geometry, coordinates: coors.slice(i + 1) },
geometry: { ...feature.geometry, coordinates: coors.slice(i + 1) }
})
coors = coors.slice(0, i + 1)
break
@ -943,7 +972,7 @@ export class GeoOperations {
}
result.push({
...feature,
geometry: { ...feature.geometry, coordinates: coors },
geometry: { ...feature.geometry, coordinates: coors }
})
}
}
@ -1117,8 +1146,8 @@ export class GeoOperations {
properties: multiLineStringFeature.properties,
geometry: {
type: "LineString",
coordinates: coors[0],
},
coordinates: coors[0]
}
}
}
return {
@ -1126,8 +1155,8 @@ export class GeoOperations {
properties: multiLineStringFeature.properties,
geometry: {
type: "MultiLineString",
coordinates: coors,
},
coordinates: coors
}
}
}

View file

@ -0,0 +1,33 @@
/**
* Various tools and types to work with the community index (https://openstreetmap.community/; https://github.com/osmlab/osm-community-index)
*/
export interface CommunityResource {
/**
* A unique identifier for the resource
* "pattern": "^[-_.A-Za-z0-9]+$"
*/
id: string,
/**
* Type of community resource (thus: platform)
*/
type: string,
/**
* included and excluded locations for this item
* See location-conflation documentation for compatible values: https://github.com/rapideditor/location-conflation#readme
*/
locationSet?,
/** Array of ISO-639-1 (2 letter) or ISO-639-3 (3 letter) codes in lowercase
* */
languageCodes?: string[]
/**
* Resource account string, required for some resource types
*/
account?: string
resolved?: { url: string, name: string, description: string } & Record<string, string>
}

View file

@ -1,4 +1,5 @@
import { BBox } from "../Logic/BBox"
import { Feature, Polygon } from "geojson"
export interface TileRange {
xstart: number
@ -80,6 +81,17 @@ export class Tiles {
return [z, x, index % factor]
}
static asGeojson(index: number): Feature<Polygon>;
static asGeojson(x: number, y: number, z: number): Feature<Polygon>;
static asGeojson(zIndex: number, x?: number, y?: number): Feature<Polygon> {
let z = zIndex
if (x === undefined) {
[z, x, y] = Tiles.tile_from_index(zIndex)
}
const bounds = Tiles.tile_bounds_lon_lat(z, x, y)
return new BBox(bounds).asGeoJson()
}
/**
* Return x, y of the tile containing (lat, lon) on the given zoom level
*/

View file

@ -6,8 +6,10 @@
import ContactLink from "./ContactLink.svelte"
import { GeoOperations } from "../../Logic/GeoOperations"
import Translations from "../i18n/Translations"
import ToSvelte from "../Base/ToSvelte.svelte"
import type { Feature, Geometry, GeometryCollection } from "@turf/turf"
import type { FeatureCollection, Polygon } from "geojson"
import type { CommunityResource } from "../../Logic/Web/CommunityIndex"
import Tr from "../Base/Tr.svelte"
export let location: Store<{ lat: number; lon: number }>
const tileToFetch: Store<string> = location.mapD((l) => {
@ -20,7 +22,10 @@
>([])
tileToFetch.addCallbackAndRun(async (url) => {
const data = await Utils.downloadJsonCached(url, 24 * 60 * 60)
const data = await Utils.downloadJsonCached<FeatureCollection<Polygon, {
nameEn: string,
resources: Record<string, CommunityResource>
}>>(url, 24 * 60 * 60)
if (data === undefined) {
return
}
@ -29,15 +34,13 @@
const filteredResources = resources.map(
(features) =>
features.filter((f) => {
return GeoOperations.inside([location.data.lon, location.data.lat], f)
}),
features.filter((f) => GeoOperations.inside([location.data.lon, location.data.lat], f)),
[location]
)
</script>
<div>
<ToSvelte construct={t.intro} />
<Tr t={t.intro} />
{#each $filteredResources as feature}
<ContactLink country={feature.properties} />
{/each}

View file

@ -3,23 +3,18 @@
// The _properties_ of a community feature
import Locale from "../i18n/Locale.js"
import Translations from "../i18n/Translations"
import ToSvelte from "../Base/ToSvelte.svelte"
import * as native from "../../assets/language_native.json"
import { TypedTranslation } from "../i18n/Translation"
import Tr from "../Base/Tr.svelte"
import type { CommunityResource } from "../../Logic/Web/CommunityIndex"
const availableTranslationTyped: TypedTranslation<{ native: string }> =
Translations.t.communityIndex.available
const availableTranslation = availableTranslationTyped.OnEveryLanguage((s, ln) =>
s.replace("{native}", native[ln] ?? ln)
)
export let country: { resources; nameEn: string }
let resources: {
id: string
resolved: Record<string, string>
languageCodes: string[]
type: string
}[] = []
export let country: { resources: Record<string, CommunityResource>; nameEn: string }
let resources: CommunityResource[] = []
$: resources = Array.from(Object.values(country?.resources ?? {}))
const language = Locale.language

View file

@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest"
describe("GeoOperations", () => {
describe("calculateOverlap", () => {
it("should not give too much overlap (regression test)", () => {
const polyGrb = {
const polyGrb: Feature<Polygon> = <any>{
type: "Feature",
properties: {
osm_id: "25189153",
@ -37,7 +37,7 @@ describe("GeoOperations", () => {
"_now:date": "2021-12-05",
"_now:datetime": "2021-12-05 21:51:40",
"_loaded:date": "2021-12-05",
"_loaded:datetime": "2021-12-05 21:51:40",
"_loaded:datetime": "2021-12-05 21:51:40"
},
geometry: {
type: "Polygon",
@ -50,21 +50,21 @@ describe("GeoOperations", () => {
[3.24329779999996, 50.837435399999855],
[3.2431881000000504, 50.83740090000025],
[3.243152699999997, 50.83738980000017],
[3.2431059999999974, 50.83730270000021],
],
],
[3.2431059999999974, 50.83730270000021]
]
]
},
id: "https://betadata.grbosm.site/grb?bbox=360935.6475626023,6592540.815539878,361088.52161917265,6592693.689596449/37",
_lon: 3.2432137000000116,
_lat: 50.83736194999996,
bbox: {
minLat: 50.83728850000007,
maxLat: 50.837435399999855,
maxLon: 3.2433214000000254,
minLat: 50.83728850000007,
minLon: 3.2431059999999974,
},
minLon: 3.2431059999999974
}
}
const polyHouse = {
const polyHouse: Feature<Polygon> = <any>{
type: "Feature",
id: "way/594963177",
properties: {
@ -95,7 +95,7 @@ describe("GeoOperations", () => {
"_loaded:date": "2021-12-05",
"_loaded:datetime": "2021-12-05 21:51:39",
_surface: "93.32785810484549",
"_surface:ha": "0",
"_surface:ha": "0"
},
geometry: {
type: "Polygon",
@ -108,9 +108,9 @@ describe("GeoOperations", () => {
[3.2431691, 50.8374252],
[3.2430936, 50.837401],
[3.243046, 50.8374112],
[3.2429993, 50.8373243],
],
],
[3.2429993, 50.8373243]
]
]
},
_lon: 3.2430937,
_lat: 50.83736395,
@ -118,8 +118,8 @@ describe("GeoOperations", () => {
maxLat: 50.8374252,
maxLon: 3.2431881,
minLat: 50.8373027,
minLon: 3.2429993,
},
minLon: 3.2429993
}
}
const p0 = turf.polygon(polyGrb.geometry.coordinates)
@ -145,11 +145,11 @@ describe("GeoOperations", () => {
[3.218560377159008, 51.21499687768525],
[3.2207456783268356, 51.21499687768525],
[3.2207456783268356, 51.21600586532159],
[3.218560377159008, 51.21600586532159],
],
[3.218560377159008, 51.21600586532159]
]
],
type: "Polygon",
},
type: "Polygon"
}
}
const line: Feature<LineString> = {
type: "Feature",
@ -157,10 +157,10 @@ describe("GeoOperations", () => {
geometry: {
coordinates: [
[3.218405371672816, 51.21499091846559],
[3.2208408127450525, 51.21560173433727],
[3.2208408127450525, 51.21560173433727]
],
type: "LineString",
},
type: "LineString"
}
}
const result = GeoOperations.clipWith(line, bbox)
expect(result.length).to.equal(1)
@ -168,10 +168,83 @@ describe("GeoOperations", () => {
const clippedLine = (<Feature<LineString>>result[0]).geometry.coordinates
const expCoordinates = [
[3.2185604, 51.215029800031594],
[3.2207457, 51.21557787977764],
[3.2207457, 51.21557787977764]
]
expect(clippedLine).to.deep.equal(expCoordinates)
})
it("clipWith should contain the full feature if it is fully contained", () => {
const bbox: Feature<Polygon> = {
type: "Feature",
properties: {},
geometry: {
coordinates: [
[
[
2.1541744759711037,
51.73994420687188
],
[
2.1541744759711037,
50.31129074222787
],
[
4.53247037641421,
50.31129074222787
],
[
4.53247037641421,
51.73994420687188
],
[
2.1541744759711037,
51.73994420687188
]
]
],
type: "Polygon"
}
}
const content: Feature<Polygon> = {
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
[
[
2.8900597545854225,
50.9035099487991
],
[
3.4872999807053873,
50.74856284865993
],
[
3.9512276563531543,
50.947206170675486
],
[
3.897902636163167,
51.25526892606362
],
[
3.188679867646016,
51.24525576870511
], [
2.8900597545854225,
50.9035099487991
]
]
],
"type": "Polygon"
}
}
const clipped = GeoOperations.clipWith(content, bbox)
expect(clipped.length).to.equal(1)
const clippedReverse = GeoOperations.clipWith(bbox, content)
expect(clippedReverse.length).to.equal(1)
}
)
})
})