@qaecy/cue-cli 0.0.44 → 0.0.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/main.js +1840 -28
- package/package.json +1 -1
package/main.js
CHANGED
|
@@ -5090,6 +5090,8 @@ var BUCKET_PERSISTENCE2 = "db_persistence_eu_west6";
|
|
|
5090
5090
|
var ENDPOINT_CONSUMPTION = "/data-views/admin/consumption";
|
|
5091
5091
|
var ENDPOINT_PROFILE_ORGANIZATIONS = "/data-views/admin/profile/organizations";
|
|
5092
5092
|
var ENDPOINT_PROFILE_API_KEYS = "/data-views/admin/profile/api-keys";
|
|
5093
|
+
var ENDPOINT_COMMANDS_PROFILE_API_KEYS = "/commands/admin/profile/api-keys";
|
|
5094
|
+
var ENDPOINT_COMMANDS_PROFILE_TERMS = "/commands/admin/profile/terms";
|
|
5093
5095
|
var ENDPOINT_ORG_MEMBERS = (orgId) => `/data-views/admin/organizations/${orgId}/members`;
|
|
5094
5096
|
var ENDPOINT_CREATE_PROJECT = "/commands/admin/project";
|
|
5095
5097
|
var ENDPOINT_SEARCH = "/assistant/search";
|
|
@@ -5221,6 +5223,28 @@ var CueAuth = class {
|
|
|
5221
5223
|
async signOut() {
|
|
5222
5224
|
await (0, import_auth3.signOut)(this._auth);
|
|
5223
5225
|
}
|
|
5226
|
+
/**
|
|
5227
|
+
* Register a new user by name and email.
|
|
5228
|
+
* The backend validates that the email domain belongs to an existing organisation,
|
|
5229
|
+
* creates the Firebase Auth account, assigns org membership, and dispatches a
|
|
5230
|
+
* "set your password" email to the address provided.
|
|
5231
|
+
* Returns the new user's UID and the organisation name on success.
|
|
5232
|
+
*/
|
|
5233
|
+
async signUp(name, email) {
|
|
5234
|
+
const response = await fetch(
|
|
5235
|
+
`${this._endpoints.gatewayUrl}/commands/admin/user/signup`,
|
|
5236
|
+
{
|
|
5237
|
+
method: "POST",
|
|
5238
|
+
headers: { "Content-Type": "application/json" },
|
|
5239
|
+
body: JSON.stringify({ name, email })
|
|
5240
|
+
}
|
|
5241
|
+
);
|
|
5242
|
+
if (!response.ok) {
|
|
5243
|
+
const body = await response.json().catch(() => ({}));
|
|
5244
|
+
throw new Error(body?.message ?? `Sign-up failed (${response.status})`);
|
|
5245
|
+
}
|
|
5246
|
+
return response.json();
|
|
5247
|
+
}
|
|
5224
5248
|
/** Currently signed-in user, or null if not authenticated */
|
|
5225
5249
|
get currentUser() {
|
|
5226
5250
|
return this._auth.currentUser;
|
|
@@ -5316,9 +5340,12 @@ var CueApi = class {
|
|
|
5316
5340
|
* The user must be authenticated before calling this.
|
|
5317
5341
|
*/
|
|
5318
5342
|
async sparql(query3, projectId, graphType) {
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5343
|
+
if (!graphType) {
|
|
5344
|
+
const project = await this.projects.getProject(projectId);
|
|
5345
|
+
graphType = project?.projectSettings?.graph?.type ?? "qlever";
|
|
5346
|
+
}
|
|
5347
|
+
const endpoint = graphType === "fuseki" ? ENDPOINT_FUSEKI_QUERY : ENDPOINT_QLEVER_QUERY;
|
|
5348
|
+
console.log(`Executing SPARQL query against ${endpoint} for project ${projectId} with graph type ${graphType}`);
|
|
5322
5349
|
const urlencoded = new URLSearchParams();
|
|
5323
5350
|
urlencoded.append("query", query3);
|
|
5324
5351
|
const response = await this._auth.authenticatedFetch(
|
|
@@ -5361,6 +5388,1730 @@ var CueApi = class {
|
|
|
5361
5388
|
}
|
|
5362
5389
|
};
|
|
5363
5390
|
|
|
5391
|
+
// libs/js/cue-gis/src/lib/models.ts
|
|
5392
|
+
var FEATURE_CATEGORIES = [
|
|
5393
|
+
"building",
|
|
5394
|
+
"cadastre",
|
|
5395
|
+
"zone",
|
|
5396
|
+
"address",
|
|
5397
|
+
"poi",
|
|
5398
|
+
"greenspace",
|
|
5399
|
+
"paved",
|
|
5400
|
+
"railway",
|
|
5401
|
+
"natural",
|
|
5402
|
+
"manmade"
|
|
5403
|
+
];
|
|
5404
|
+
var NOMINATIM_FEATURE_CATEGORIES = [
|
|
5405
|
+
"address",
|
|
5406
|
+
"poi",
|
|
5407
|
+
"railway",
|
|
5408
|
+
"natural",
|
|
5409
|
+
"manmade"
|
|
5410
|
+
];
|
|
5411
|
+
var FEATURE_CATEGORY_DESCRIPTORS = {
|
|
5412
|
+
address: {
|
|
5413
|
+
category: "address",
|
|
5414
|
+
label: "Address",
|
|
5415
|
+
description: "Official addresses, streets, and addressable access points.",
|
|
5416
|
+
preferredColor: "#1d4ed8"
|
|
5417
|
+
},
|
|
5418
|
+
poi: {
|
|
5419
|
+
category: "poi",
|
|
5420
|
+
label: "Points of interest",
|
|
5421
|
+
description: "Places and amenities relevant to everyday operations and access.",
|
|
5422
|
+
preferredColor: "#059669"
|
|
5423
|
+
},
|
|
5424
|
+
railway: {
|
|
5425
|
+
category: "railway",
|
|
5426
|
+
label: "Railway",
|
|
5427
|
+
description: "Rail infrastructure, stations, and rail-adjacent transport context.",
|
|
5428
|
+
preferredColor: "#dc2626"
|
|
5429
|
+
},
|
|
5430
|
+
natural: {
|
|
5431
|
+
category: "natural",
|
|
5432
|
+
label: "Natural features",
|
|
5433
|
+
description: "Natural and hydrological features such as watercourses, forests, and land cover.",
|
|
5434
|
+
preferredColor: "#0ea5e9"
|
|
5435
|
+
},
|
|
5436
|
+
manmade: {
|
|
5437
|
+
category: "manmade",
|
|
5438
|
+
label: "Built features",
|
|
5439
|
+
description: "Structures and man-made elements not covered by more specific categories.",
|
|
5440
|
+
preferredColor: "#ca8a04"
|
|
5441
|
+
},
|
|
5442
|
+
cadastre: {
|
|
5443
|
+
category: "cadastre",
|
|
5444
|
+
label: "Cadastre",
|
|
5445
|
+
description: "Land parcels, legal boundaries, official surveying, and public-law land restrictions.",
|
|
5446
|
+
preferredColor: "#7c3aed"
|
|
5447
|
+
},
|
|
5448
|
+
building: {
|
|
5449
|
+
category: "building",
|
|
5450
|
+
label: "Buildings",
|
|
5451
|
+
description: "Building footprints and structures from official registry or cadastral data.",
|
|
5452
|
+
preferredColor: "#f97316"
|
|
5453
|
+
},
|
|
5454
|
+
greenspace: {
|
|
5455
|
+
category: "greenspace",
|
|
5456
|
+
label: "Green spaces",
|
|
5457
|
+
description: "Parks, gardens, playgrounds, sports facilities, and other vegetated open areas.",
|
|
5458
|
+
preferredColor: "#16a34a"
|
|
5459
|
+
},
|
|
5460
|
+
paved: {
|
|
5461
|
+
category: "paved",
|
|
5462
|
+
label: "Paved surfaces",
|
|
5463
|
+
description: "Courtyards, sidewalks, parking areas, and other sealed ground surfaces.",
|
|
5464
|
+
preferredColor: "#78716c"
|
|
5465
|
+
},
|
|
5466
|
+
zone: {
|
|
5467
|
+
category: "zone",
|
|
5468
|
+
label: "Planning zones",
|
|
5469
|
+
description: "Land-use zones, local plans, and public-law planning constraints (Nutzungsplanung / Lokalplan).",
|
|
5470
|
+
preferredColor: "#db2777"
|
|
5471
|
+
}
|
|
5472
|
+
};
|
|
5473
|
+
function featureCategoryDescriptor(category) {
|
|
5474
|
+
return FEATURE_CATEGORY_DESCRIPTORS[category];
|
|
5475
|
+
}
|
|
5476
|
+
var ZONE_PLAN_TYPE_COLORS = {
|
|
5477
|
+
"land-use-plan": "#f59e0b",
|
|
5478
|
+
// amber – primary zoning
|
|
5479
|
+
"local-plan": "#ec4899",
|
|
5480
|
+
// pink – Danish lokalplan
|
|
5481
|
+
"design-plan": "#3b82f6",
|
|
5482
|
+
// blue – Gestaltungsplan
|
|
5483
|
+
"development-plan": "#0891b2",
|
|
5484
|
+
// cyan – Bebauungsplan
|
|
5485
|
+
"special-use-plan": "#f97316",
|
|
5486
|
+
// orange – Sondernutzungsplan
|
|
5487
|
+
"open-space-zone": "#22c55e",
|
|
5488
|
+
// green – Freihaltezone
|
|
5489
|
+
"overlay-regulation": "#a855f7",
|
|
5490
|
+
// purple – overlaying regulations
|
|
5491
|
+
"unzoned": "#94a3b8",
|
|
5492
|
+
// slate-400 – nicht zonierte Fläche
|
|
5493
|
+
"municipal-plan-framework": "#6366f1",
|
|
5494
|
+
// indigo – Kommuneplanramme
|
|
5495
|
+
"neighbourhood-conservation-plan": "#14b8a6",
|
|
5496
|
+
// teal – Quartiererhaltungszonenplan
|
|
5497
|
+
"noise-sensitivity-plan": "#eab308",
|
|
5498
|
+
// yellow – Lärmempfindlichkeitsstufenplan
|
|
5499
|
+
"water-protection-plan": "#06b6d4",
|
|
5500
|
+
// sky – Gewässerschutzzonenplan
|
|
5501
|
+
"nature-landscape-plan": "#16a34a"
|
|
5502
|
+
// dark-green – Natur- und Landschaftsschutz
|
|
5503
|
+
};
|
|
5504
|
+
function bboxIntersects(a5, b) {
|
|
5505
|
+
return a5[0] < b[2] && a5[2] > b[0] && a5[1] < b[3] && a5[3] > b[1];
|
|
5506
|
+
}
|
|
5507
|
+
|
|
5508
|
+
// libs/js/cue-gis/src/lib/nominatim-adapter.ts
|
|
5509
|
+
var NOMINATIM_BASE_URL = "https://nominatim.openstreetmap.org";
|
|
5510
|
+
var DEFAULT_USER_AGENT = "cue-gis/0.0.1";
|
|
5511
|
+
var LAYER_PROBE_QUERY = {
|
|
5512
|
+
address: "street",
|
|
5513
|
+
poi: "shop",
|
|
5514
|
+
railway: "station",
|
|
5515
|
+
natural: "park",
|
|
5516
|
+
manmade: "building",
|
|
5517
|
+
cadastre: "parcel",
|
|
5518
|
+
building: "building",
|
|
5519
|
+
greenspace: "park",
|
|
5520
|
+
paved: "road",
|
|
5521
|
+
zone: "boundary"
|
|
5522
|
+
};
|
|
5523
|
+
var NOMINATIM_SOURCE_ID = "nominatim";
|
|
5524
|
+
function toLayerId(category) {
|
|
5525
|
+
return `${NOMINATIM_SOURCE_ID}:${category}`;
|
|
5526
|
+
}
|
|
5527
|
+
function toLayerDescriptor(category) {
|
|
5528
|
+
const descriptor = featureCategoryDescriptor(category);
|
|
5529
|
+
return {
|
|
5530
|
+
id: toLayerId(category),
|
|
5531
|
+
sourceId: NOMINATIM_SOURCE_ID,
|
|
5532
|
+
sourceLayerId: category,
|
|
5533
|
+
category,
|
|
5534
|
+
preferredColor: descriptor.preferredColor,
|
|
5535
|
+
tier: "raw",
|
|
5536
|
+
label: descriptor.label,
|
|
5537
|
+
description: `Nominatim ${category} layer`,
|
|
5538
|
+
labelKey: `gis.layer.${NOMINATIM_SOURCE_ID}.${category}.label`,
|
|
5539
|
+
descriptionKey: `gis.layer.${NOMINATIM_SOURCE_ID}.${category}.description`
|
|
5540
|
+
};
|
|
5541
|
+
}
|
|
5542
|
+
var NominatimAdapter = class {
|
|
5543
|
+
baseUrl;
|
|
5544
|
+
userAgent;
|
|
5545
|
+
email;
|
|
5546
|
+
limit;
|
|
5547
|
+
constructor(options = {}) {
|
|
5548
|
+
this.baseUrl = options.baseUrl ?? NOMINATIM_BASE_URL;
|
|
5549
|
+
this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;
|
|
5550
|
+
this.email = options.email;
|
|
5551
|
+
this.limit = Math.min(options.limit ?? 40, 40);
|
|
5552
|
+
}
|
|
5553
|
+
async listFeatureCategoryDescriptors(bbox) {
|
|
5554
|
+
const checks = await Promise.allSettled(
|
|
5555
|
+
NOMINATIM_FEATURE_CATEGORIES.map(async (category) => {
|
|
5556
|
+
const results = await this.search(bbox, category);
|
|
5557
|
+
return { category, hasResults: results.length > 0 };
|
|
5558
|
+
})
|
|
5559
|
+
);
|
|
5560
|
+
return checks.filter(
|
|
5561
|
+
(r) => r.status === "fulfilled" && r.value.hasResults
|
|
5562
|
+
).map((r) => featureCategoryDescriptor(r.value.category));
|
|
5563
|
+
}
|
|
5564
|
+
async listAvailableLayers(bbox) {
|
|
5565
|
+
const categories = await this.listFeatureCategoryDescriptors(bbox);
|
|
5566
|
+
return categories.map((category) => toLayerDescriptor(category.category));
|
|
5567
|
+
}
|
|
5568
|
+
async getFeaturesForLayer(bbox, layerId) {
|
|
5569
|
+
const category = FEATURE_CATEGORIES.find((candidate) => toLayerId(candidate) === layerId);
|
|
5570
|
+
if (!category) {
|
|
5571
|
+
return [];
|
|
5572
|
+
}
|
|
5573
|
+
return this.search(bbox, category);
|
|
5574
|
+
}
|
|
5575
|
+
async listFeatureCategories(bbox) {
|
|
5576
|
+
const descriptors = await this.listFeatureCategoryDescriptors(bbox);
|
|
5577
|
+
return descriptors.map((descriptor) => descriptor.category);
|
|
5578
|
+
}
|
|
5579
|
+
async getFeaturesOfCategory(bbox, category) {
|
|
5580
|
+
return this.getFeaturesForLayer(bbox, toLayerId(category));
|
|
5581
|
+
}
|
|
5582
|
+
/**
|
|
5583
|
+
* Low-level search against the Nominatim API. Uses a per-layer probe query
|
|
5584
|
+
* so bounded viewbox searches return meaningful results.
|
|
5585
|
+
*/
|
|
5586
|
+
async search(bbox, layer) {
|
|
5587
|
+
const [west, south, east, north] = bbox;
|
|
5588
|
+
const q = layer ? LAYER_PROBE_QUERY[layer] : "place";
|
|
5589
|
+
const params = new URLSearchParams({
|
|
5590
|
+
q,
|
|
5591
|
+
format: "jsonv2",
|
|
5592
|
+
viewbox: `${west},${north},${east},${south}`,
|
|
5593
|
+
bounded: "1",
|
|
5594
|
+
limit: String(this.limit),
|
|
5595
|
+
addressdetails: "1",
|
|
5596
|
+
extratags: "1",
|
|
5597
|
+
dedupe: "1"
|
|
5598
|
+
});
|
|
5599
|
+
if (layer) {
|
|
5600
|
+
params.set("layer", layer);
|
|
5601
|
+
}
|
|
5602
|
+
if (this.email) {
|
|
5603
|
+
params.set("email", this.email);
|
|
5604
|
+
}
|
|
5605
|
+
const url = `${this.baseUrl}/search?${params.toString()}`;
|
|
5606
|
+
const response = await fetch(url, {
|
|
5607
|
+
headers: {
|
|
5608
|
+
"User-Agent": this.userAgent,
|
|
5609
|
+
Accept: "application/json"
|
|
5610
|
+
}
|
|
5611
|
+
});
|
|
5612
|
+
if (!response.ok) {
|
|
5613
|
+
throw new Error(
|
|
5614
|
+
`Nominatim request failed: ${response.status} ${response.statusText}`
|
|
5615
|
+
);
|
|
5616
|
+
}
|
|
5617
|
+
const results = await response.json();
|
|
5618
|
+
return results.map((r) => this.toGisFeature(r, layer));
|
|
5619
|
+
}
|
|
5620
|
+
toGisFeature(r, category) {
|
|
5621
|
+
const [s, n, w, e] = r.boundingbox;
|
|
5622
|
+
const resolvedCategory = category ?? r.category;
|
|
5623
|
+
const descriptor = featureCategoryDescriptor(resolvedCategory);
|
|
5624
|
+
return {
|
|
5625
|
+
id: `${r.osm_type}/${r.osm_id}`,
|
|
5626
|
+
sourceId: NOMINATIM_SOURCE_ID,
|
|
5627
|
+
sourceFeatureId: `${r.osm_type}/${r.osm_id}`,
|
|
5628
|
+
layerId: toLayerId(resolvedCategory),
|
|
5629
|
+
name: r.name ?? r.display_name.split(",")[0].trim(),
|
|
5630
|
+
category: resolvedCategory,
|
|
5631
|
+
preferredColor: descriptor.preferredColor,
|
|
5632
|
+
type: r.type,
|
|
5633
|
+
lat: parseFloat(r.lat),
|
|
5634
|
+
lon: parseFloat(r.lon),
|
|
5635
|
+
displayName: r.display_name,
|
|
5636
|
+
bbox: [parseFloat(w), parseFloat(s), parseFloat(e), parseFloat(n)],
|
|
5637
|
+
tier: "raw",
|
|
5638
|
+
originalData: r
|
|
5639
|
+
};
|
|
5640
|
+
}
|
|
5641
|
+
};
|
|
5642
|
+
|
|
5643
|
+
// libs/js/cue-gis/src/lib/use-classifier.ts
|
|
5644
|
+
var DK_BBR_BUILDING = {
|
|
5645
|
+
// 100s – Residential
|
|
5646
|
+
"110": "residential",
|
|
5647
|
+
"120": "residential",
|
|
5648
|
+
"121": "residential",
|
|
5649
|
+
"122": "residential",
|
|
5650
|
+
"130": "residential",
|
|
5651
|
+
"131": "residential",
|
|
5652
|
+
"132": "residential",
|
|
5653
|
+
"140": "residential",
|
|
5654
|
+
"150": "residential",
|
|
5655
|
+
"160": "residential",
|
|
5656
|
+
"190": "residential",
|
|
5657
|
+
// 200s – Recreational / holiday
|
|
5658
|
+
"210": "recreational",
|
|
5659
|
+
"212": "recreational",
|
|
5660
|
+
"220": "recreational",
|
|
5661
|
+
"221": "recreational",
|
|
5662
|
+
"230": "recreational",
|
|
5663
|
+
"290": "residential",
|
|
5664
|
+
// 300s – Agricultural
|
|
5665
|
+
"310": "agricultural",
|
|
5666
|
+
"311": "agricultural",
|
|
5667
|
+
"312": "agricultural",
|
|
5668
|
+
"320": "agricultural",
|
|
5669
|
+
"321": "agricultural",
|
|
5670
|
+
"322": "agricultural",
|
|
5671
|
+
"330": "agricultural",
|
|
5672
|
+
"390": "agricultural",
|
|
5673
|
+
// 400s – Commercial
|
|
5674
|
+
"410": "commercial",
|
|
5675
|
+
"411": "commercial",
|
|
5676
|
+
"420": "commercial",
|
|
5677
|
+
"421": "commercial",
|
|
5678
|
+
"422": "commercial",
|
|
5679
|
+
"430": "commercial",
|
|
5680
|
+
"431": "commercial",
|
|
5681
|
+
"432": "commercial",
|
|
5682
|
+
"440": "commercial",
|
|
5683
|
+
"490": "commercial",
|
|
5684
|
+
// 500s – Industrial / infrastructure
|
|
5685
|
+
"510": "industrial",
|
|
5686
|
+
"511": "industrial",
|
|
5687
|
+
"519": "industrial",
|
|
5688
|
+
"520": "infrastructure",
|
|
5689
|
+
"521": "infrastructure",
|
|
5690
|
+
"522": "infrastructure",
|
|
5691
|
+
"529": "infrastructure",
|
|
5692
|
+
"530": "infrastructure",
|
|
5693
|
+
"531": "infrastructure",
|
|
5694
|
+
"532": "infrastructure",
|
|
5695
|
+
"533": "infrastructure",
|
|
5696
|
+
"534": "infrastructure",
|
|
5697
|
+
"535": "infrastructure",
|
|
5698
|
+
"540": "infrastructure",
|
|
5699
|
+
"585": "infrastructure",
|
|
5700
|
+
"590": "industrial",
|
|
5701
|
+
// 600s – Public / institutional
|
|
5702
|
+
"610": "public",
|
|
5703
|
+
"620": "public",
|
|
5704
|
+
"621": "public",
|
|
5705
|
+
"622": "public",
|
|
5706
|
+
"630": "public",
|
|
5707
|
+
"640": "public",
|
|
5708
|
+
"650": "recreational",
|
|
5709
|
+
"660": "public",
|
|
5710
|
+
"670": "public",
|
|
5711
|
+
"680": "public",
|
|
5712
|
+
"690": "public",
|
|
5713
|
+
// 900s – Accessory / utility
|
|
5714
|
+
"910": "other",
|
|
5715
|
+
"920": "other",
|
|
5716
|
+
"930": "other",
|
|
5717
|
+
"940": "other",
|
|
5718
|
+
"950": "infrastructure",
|
|
5719
|
+
// Teknikbygning (utility / technical building)
|
|
5720
|
+
"960": "agricultural",
|
|
5721
|
+
"970": "agricultural",
|
|
5722
|
+
"990": "other"
|
|
5723
|
+
};
|
|
5724
|
+
var CH_GWR_BUILDING = {
|
|
5725
|
+
"1010": "residential",
|
|
5726
|
+
"1020": "mixed",
|
|
5727
|
+
"1030": "residential",
|
|
5728
|
+
"1040": "other",
|
|
5729
|
+
"1060": "recreational",
|
|
5730
|
+
"1080": "residential",
|
|
5731
|
+
"1110": "commercial",
|
|
5732
|
+
"1120": "commercial",
|
|
5733
|
+
"1130": "commercial",
|
|
5734
|
+
"1140": "industrial",
|
|
5735
|
+
"1150": "infrastructure",
|
|
5736
|
+
"1160": "infrastructure",
|
|
5737
|
+
"1210": "public",
|
|
5738
|
+
"1220": "public",
|
|
5739
|
+
"1230": "public",
|
|
5740
|
+
"1240": "recreational",
|
|
5741
|
+
"1241": "public",
|
|
5742
|
+
"1242": "recreational",
|
|
5743
|
+
"1251": "public",
|
|
5744
|
+
"1261": "agricultural",
|
|
5745
|
+
"1262": "agricultural",
|
|
5746
|
+
"1263": "agricultural",
|
|
5747
|
+
"1275": "other",
|
|
5748
|
+
"1276": "other",
|
|
5749
|
+
"1277": "infrastructure",
|
|
5750
|
+
"1278": "infrastructure",
|
|
5751
|
+
"1281": "infrastructure",
|
|
5752
|
+
"1282": "infrastructure",
|
|
5753
|
+
"9999": "other"
|
|
5754
|
+
};
|
|
5755
|
+
var CH_GWR_GKLAS = {
|
|
5756
|
+
// Residential
|
|
5757
|
+
"1110": "residential",
|
|
5758
|
+
// Einfamilienhaus
|
|
5759
|
+
"1121": "recreational",
|
|
5760
|
+
// Ferienhaus / Sommerwohnsitz
|
|
5761
|
+
"1122": "residential",
|
|
5762
|
+
// Mehrfamilienhaus
|
|
5763
|
+
"1130": "mixed",
|
|
5764
|
+
// Wohn- und Geschäftshaus
|
|
5765
|
+
"1140": "mixed",
|
|
5766
|
+
// Wohn- und Landwirtschaftsgebäude
|
|
5767
|
+
// Hospitality
|
|
5768
|
+
"1211": "commercial",
|
|
5769
|
+
// Hotel, Motel
|
|
5770
|
+
"1212": "commercial",
|
|
5771
|
+
// Gaststätten, Restaurant
|
|
5772
|
+
"1213": "mixed",
|
|
5773
|
+
// Pension, Heim (care home / pension)
|
|
5774
|
+
// Office / commercial
|
|
5775
|
+
"1220": "commercial",
|
|
5776
|
+
// Büro- und Verwaltungsgebäude
|
|
5777
|
+
"1230": "commercial",
|
|
5778
|
+
// Handelsbau (retail)
|
|
5779
|
+
"1231": "commercial",
|
|
5780
|
+
// Grosshandel / Grossmarkt
|
|
5781
|
+
// Infrastructure / transport
|
|
5782
|
+
"1241": "infrastructure",
|
|
5783
|
+
// Bahnhof, Flughafen
|
|
5784
|
+
// Leisure
|
|
5785
|
+
"1242": "recreational",
|
|
5786
|
+
// Freizeitgebäude
|
|
5787
|
+
"1264": "recreational",
|
|
5788
|
+
// Sportgebäude
|
|
5789
|
+
// Industrial
|
|
5790
|
+
"1251": "industrial",
|
|
5791
|
+
// Fabrik, Werkstatt
|
|
5792
|
+
"1252": "industrial",
|
|
5793
|
+
// Lagerhalle, Silo
|
|
5794
|
+
// Public / institutional
|
|
5795
|
+
"1261": "public",
|
|
5796
|
+
// Schulen, Bildungsstätten
|
|
5797
|
+
"1262": "public",
|
|
5798
|
+
// Spitäler, Kliniken
|
|
5799
|
+
"1263": "public",
|
|
5800
|
+
// Universität, Forschungsstätte
|
|
5801
|
+
"1265": "public",
|
|
5802
|
+
// Polizei, Militär, Gefängnis
|
|
5803
|
+
"1272": "public",
|
|
5804
|
+
// Religiöse Gebäude und Kultbauten
|
|
5805
|
+
"1273": "public",
|
|
5806
|
+
// Öffentliche Verwaltungsgebäude
|
|
5807
|
+
// Agricultural
|
|
5808
|
+
"1271": "agricultural",
|
|
5809
|
+
// Landwirtschaftsgebäude
|
|
5810
|
+
// Other
|
|
5811
|
+
"1274": "other",
|
|
5812
|
+
// Anderes Gebäude
|
|
5813
|
+
"1281": "infrastructure",
|
|
5814
|
+
// Empfangsgebäude Bahn/Bus
|
|
5815
|
+
"1282": "infrastructure"
|
|
5816
|
+
// Parkhaus, Garage
|
|
5817
|
+
};
|
|
5818
|
+
var KEYWORD_RULES = [
|
|
5819
|
+
// "Gebäude" is the Swiss AV BoFlaeche land-cover label for a building footprint.
|
|
5820
|
+
// It does not encode the actual use type, so map it to 'other' as a safe fallback.
|
|
5821
|
+
[/^geb[äa]ude$/i, "other"],
|
|
5822
|
+
[/wohn|resident|housing|bolig|enfamil|etagebolig|rækkehus|lejlighed/i, "residential"],
|
|
5823
|
+
[/büro|office|handel|commercial|butik|forretning|kontor|supermarked|lager|hotel|restaurant/i, "commercial"],
|
|
5824
|
+
[/industri|fabrik|industrial|gewerbe|produktion|fabrik|fremstill/i, "industrial"],
|
|
5825
|
+
[/landwirtschaft|agrar|agricultural|landbrug|skov|dyrk|stald|lade|drivhus/i, "agricultural"],
|
|
5826
|
+
[/schule|school|hospital|gesundheit|sundhed|kirche|church|offentlig|uddannelse|kultur|social|plejehjem/i, "public"],
|
|
5827
|
+
[/freizeit|recreation|sport|sommerhus|ferienhaus|fritid|kolonihave/i, "recreational"],
|
|
5828
|
+
[/infrastruktur|verkehr|transport|energy|energie|forsyning|teknik|teknisk/i, "infrastructure"],
|
|
5829
|
+
[/gemischt|mixed|blandet/i, "mixed"]
|
|
5830
|
+
];
|
|
5831
|
+
function classifyByText(text) {
|
|
5832
|
+
for (const [pattern, use] of KEYWORD_RULES) {
|
|
5833
|
+
if (pattern.test(text))
|
|
5834
|
+
return use;
|
|
5835
|
+
}
|
|
5836
|
+
return void 0;
|
|
5837
|
+
}
|
|
5838
|
+
function classifyBuildingUse(code, sourceId) {
|
|
5839
|
+
if (code == null)
|
|
5840
|
+
return void 0;
|
|
5841
|
+
const key = String(code).trim();
|
|
5842
|
+
if (sourceId === "swiss-gwr") {
|
|
5843
|
+
return CH_GWR_GKLAS[key] ?? classifyByText(key);
|
|
5844
|
+
}
|
|
5845
|
+
if (sourceId === "danish-matrikel") {
|
|
5846
|
+
return DK_BBR_BUILDING[key] ?? classifyByText(key);
|
|
5847
|
+
}
|
|
5848
|
+
if (sourceId === "zurich-wfs" || sourceId === "swiss-av-wfs") {
|
|
5849
|
+
return CH_GWR_BUILDING[key] ?? classifyByText(key);
|
|
5850
|
+
}
|
|
5851
|
+
return CH_GWR_BUILDING[key] ?? DK_BBR_BUILDING[key] ?? classifyByText(key);
|
|
5852
|
+
}
|
|
5853
|
+
function classifyPlotUse(use, _sourceId) {
|
|
5854
|
+
if (use == null)
|
|
5855
|
+
return void 0;
|
|
5856
|
+
return classifyByText(String(use).trim());
|
|
5857
|
+
}
|
|
5858
|
+
function normKey(raw) {
|
|
5859
|
+
return raw.trim().toLowerCase().replace(/[\s_\-]/g, "");
|
|
5860
|
+
}
|
|
5861
|
+
var ZONE_LEGAL_STATUS_MAP = {
|
|
5862
|
+
// Swiss ÖREB rechtsstatus
|
|
5863
|
+
"inkraft": "in-force",
|
|
5864
|
+
"laufendeanderung": "amendment-pending",
|
|
5865
|
+
"laufende\xE4nderung": "amendment-pending",
|
|
5866
|
+
"aenderungohnevorvirkung": "amendment-pending",
|
|
5867
|
+
// guard against common typo
|
|
5868
|
+
"aenderungohnevorwirkung": "amendment-pending",
|
|
5869
|
+
"\xE4nderungohnevorwirkung": "amendment-pending",
|
|
5870
|
+
"aenderungmitvorwirkung": "amendment-in-effect",
|
|
5871
|
+
"\xE4nderungmitvorwirkung": "amendment-in-effect",
|
|
5872
|
+
"\xF6ffentlicheauflage": "proposed",
|
|
5873
|
+
"offentlicheauflage": "proposed",
|
|
5874
|
+
"aufgehoben": "repealed",
|
|
5875
|
+
// Danish plandata.dk status
|
|
5876
|
+
"vedtaget": "in-force",
|
|
5877
|
+
"forslag": "proposed",
|
|
5878
|
+
"aflyst": "repealed"
|
|
5879
|
+
};
|
|
5880
|
+
function classifyZoneLegalStatus(raw) {
|
|
5881
|
+
if (!raw)
|
|
5882
|
+
return void 0;
|
|
5883
|
+
return ZONE_LEGAL_STATUS_MAP[normKey(raw)];
|
|
5884
|
+
}
|
|
5885
|
+
var ZONE_PLAN_TYPE_EXACT = {
|
|
5886
|
+
// Swiss Nutzungsplanung (typ_gde_bezeichnung and common variants)
|
|
5887
|
+
"grundnutzungszonenplan": "land-use-plan",
|
|
5888
|
+
"nutzungszonenplan": "land-use-plan",
|
|
5889
|
+
"zonenplan": "land-use-plan",
|
|
5890
|
+
"gestaltungsplan": "design-plan",
|
|
5891
|
+
"bebauungsplan": "development-plan",
|
|
5892
|
+
"sondernutzungsplan": "special-use-plan",
|
|
5893
|
+
"quartiererhaltungszonenplan": "neighbourhood-conservation-plan",
|
|
5894
|
+
"l\xE4rmempfindlichkeitsstufenplan": "noise-sensitivity-plan",
|
|
5895
|
+
"laermempfindlichkeitsstufenplan": "noise-sensitivity-plan",
|
|
5896
|
+
"gew\xE4sserschutzzonenplan": "water-protection-plan",
|
|
5897
|
+
"gewasserschutzzonenplan": "water-protection-plan",
|
|
5898
|
+
"grundwasserschutzzonenplan": "water-protection-plan",
|
|
5899
|
+
"gew\xE4sserschutzplan": "water-protection-plan",
|
|
5900
|
+
"naturundlandschaftsschutzzonenplan": "nature-landscape-plan",
|
|
5901
|
+
"natur-undlandschaftsschutzzonenplan": "nature-landscape-plan",
|
|
5902
|
+
"landschaftsschutzzonenplan": "nature-landscape-plan",
|
|
5903
|
+
// Freihaltezone — open space / clearance zone within the Grundnutzungszonenplan
|
|
5904
|
+
"freihaltezone": "open-space-zone",
|
|
5905
|
+
// Simple Grundnutzung zone type designations (municipality labels from ogd-0156)
|
|
5906
|
+
"kernzone": "land-use-plan",
|
|
5907
|
+
"wald": "land-use-plan",
|
|
5908
|
+
"gew\xE4sser": "land-use-plan",
|
|
5909
|
+
"gewasser": "land-use-plan",
|
|
5910
|
+
"reservezone": "land-use-plan",
|
|
5911
|
+
"bahnareal": "land-use-plan",
|
|
5912
|
+
"erholungszone": "land-use-plan",
|
|
5913
|
+
// Road / transport area designations
|
|
5914
|
+
"strassen(weitere)": "land-use-plan",
|
|
5915
|
+
"strassen": "land-use-plan",
|
|
5916
|
+
// Unzoned areas
|
|
5917
|
+
"nichtzoniertfl\xE4che": "unzoned",
|
|
5918
|
+
"nichtzoniertefl\xE4che": "unzoned",
|
|
5919
|
+
"nichtzoniertflache": "unzoned",
|
|
5920
|
+
"nichtzonierteflache": "unzoned",
|
|
5921
|
+
"nichtzoniertgem\xE4ssbzo2016": "unzoned",
|
|
5922
|
+
"nichtzoniertgemassbzo2016": "unzoned",
|
|
5923
|
+
// Overlay building/development regulations (Überlagernde Festlegungen)
|
|
5924
|
+
"hochh\xE4user": "overlay-regulation",
|
|
5925
|
+
"hochhauser": "overlay-regulation",
|
|
5926
|
+
"areal\xFCberbauungenzul\xE4ssig": "overlay-regulation",
|
|
5927
|
+
"arealuberbauungenzulassig": "overlay-regulation",
|
|
5928
|
+
"erh\xF6hteausnutzung": "overlay-regulation",
|
|
5929
|
+
"erhohteausnutzung": "overlay-regulation",
|
|
5930
|
+
"erdgeschossnutzung": "overlay-regulation",
|
|
5931
|
+
"freifl\xE4chenzifferverlegungsgebiet": "overlay-regulation",
|
|
5932
|
+
"freiflachenzifferverlegungsgebiet": "overlay-regulation",
|
|
5933
|
+
"baumschutz": "overlay-regulation",
|
|
5934
|
+
// Special building regulations (Sonderbauvorschriften)
|
|
5935
|
+
"sonderbauvorschriften": "overlay-regulation",
|
|
5936
|
+
// Danish plandata.dk
|
|
5937
|
+
"lokalplan": "local-plan",
|
|
5938
|
+
"kommuneplanramme": "municipal-plan-framework"
|
|
5939
|
+
};
|
|
5940
|
+
var ZONE_PLAN_TYPE_KEYWORDS = [
|
|
5941
|
+
[/quartiererhalt/i, "neighbourhood-conservation-plan"],
|
|
5942
|
+
[/lärmempfindlich|laermempfindlich/i, "noise-sensitivity-plan"],
|
|
5943
|
+
[/gewässerschutz|grundwasserschutz/i, "water-protection-plan"],
|
|
5944
|
+
[/natur.*landschaft|landschaft.*schutz/i, "nature-landscape-plan"],
|
|
5945
|
+
[/gestaltungsplan/i, "design-plan"],
|
|
5946
|
+
[/bebauungsplan/i, "development-plan"],
|
|
5947
|
+
[/sondernutzung/i, "special-use-plan"],
|
|
5948
|
+
// Freihaltezone / open-space clearance zones (before general zonenplan catch-all)
|
|
5949
|
+
[/freihalt/i, "open-space-zone"],
|
|
5950
|
+
// Zone für öffentliche Bauten — public-facilities land-use zone
|
|
5951
|
+
[/öffentliche.*bauten|zone.*öffentlich/i, "land-use-plan"],
|
|
5952
|
+
// Overlaying building/development regulations
|
|
5953
|
+
[/hochhaus/i, "overlay-regulation"],
|
|
5954
|
+
[/arealüberbauung|arealuberbauung/i, "overlay-regulation"],
|
|
5955
|
+
[/erhöhte.*ausnutzung|ausnutzung/i, "overlay-regulation"],
|
|
5956
|
+
[/erdgeschossnutzung/i, "overlay-regulation"],
|
|
5957
|
+
[/freiflächenzifferverlegung|freiflachenzifferverlegung/i, "overlay-regulation"],
|
|
5958
|
+
[/erhaltenswert/i, "overlay-regulation"],
|
|
5959
|
+
[/m[\u00e4a]ssig.*st[\u00f6o]rend|st[\u00f6o]rendes.*gewerbe/i, "overlay-regulation"],
|
|
5960
|
+
// Grundnutzung zone type designations (municipality labels, ogd-0156)
|
|
5961
|
+
[/wohnzone/i, "land-use-plan"],
|
|
5962
|
+
[/kernzone|zentrumszone/i, "land-use-plan"],
|
|
5963
|
+
[/erholungszone|erholungs/i, "land-use-plan"],
|
|
5964
|
+
[/stra[sß]en/i, "land-use-plan"],
|
|
5965
|
+
[/landwirtschaft/i, "land-use-plan"],
|
|
5966
|
+
[/\bwald\b/i, "land-use-plan"],
|
|
5967
|
+
[/\bgew[\u00e4a]sser\b/i, "land-use-plan"],
|
|
5968
|
+
// Unzoned areas
|
|
5969
|
+
[/nicht\s*zoniert/i, "unzoned"],
|
|
5970
|
+
// Tree / vegetation protection overlay
|
|
5971
|
+
[/baumschutz/i, "overlay-regulation"],
|
|
5972
|
+
// Special building regulations not under standard building ordinance
|
|
5973
|
+
[/sonderbauvorschrift/i, "overlay-regulation"],
|
|
5974
|
+
[/kommuneplan/i, "municipal-plan-framework"],
|
|
5975
|
+
[/lokalplan/i, "local-plan"],
|
|
5976
|
+
[/zonenplan|nutzungsplan|nutzungszone/i, "land-use-plan"],
|
|
5977
|
+
// Catch-all: any remaining Xzone / Xareal designation from the Grundnutzungszonenplan
|
|
5978
|
+
[/zone$|areal$/i, "land-use-plan"]
|
|
5979
|
+
];
|
|
5980
|
+
function classifyZonePlanType(raw) {
|
|
5981
|
+
if (!raw)
|
|
5982
|
+
return void 0;
|
|
5983
|
+
const key = normKey(raw);
|
|
5984
|
+
if (key in ZONE_PLAN_TYPE_EXACT)
|
|
5985
|
+
return ZONE_PLAN_TYPE_EXACT[key];
|
|
5986
|
+
for (const [pattern, planType] of ZONE_PLAN_TYPE_KEYWORDS) {
|
|
5987
|
+
if (pattern.test(raw))
|
|
5988
|
+
return planType;
|
|
5989
|
+
}
|
|
5990
|
+
console.warn(
|
|
5991
|
+
`[cue-gis] Unknown zone plan type \u2014 add "${raw}" to classifyZonePlanType: no ZonePlanType mapping found.`
|
|
5992
|
+
);
|
|
5993
|
+
return void 0;
|
|
5994
|
+
}
|
|
5995
|
+
|
|
5996
|
+
// libs/js/cue-gis/src/lib/zurich-maps-adapter.ts
|
|
5997
|
+
var WFS_BASE_URL = "https://maps.zh.ch/wfs/OGDZHWFS";
|
|
5998
|
+
var ZURICH_SOURCE_ID = "zurich-wfs";
|
|
5999
|
+
var ZURICH_CANTON_BBOX = [8.35, 47.15, 8.95, 47.7];
|
|
6000
|
+
var SWITZERLAND_BBOX = [5.9, 45.7, 10.55, 47.85];
|
|
6001
|
+
function entryTypeName(entry) {
|
|
6002
|
+
return typeof entry === "string" ? entry : entry.typeName;
|
|
6003
|
+
}
|
|
6004
|
+
function entryCqlFilter(entry) {
|
|
6005
|
+
return typeof entry === "string" ? void 0 : entry.cqlFilter;
|
|
6006
|
+
}
|
|
6007
|
+
function firstCoordinate(geometry) {
|
|
6008
|
+
if (!geometry)
|
|
6009
|
+
return [0, 0];
|
|
6010
|
+
const coords = geometry.coordinates;
|
|
6011
|
+
switch (geometry.type) {
|
|
6012
|
+
case "Point":
|
|
6013
|
+
return coords;
|
|
6014
|
+
case "MultiPoint":
|
|
6015
|
+
case "LineString":
|
|
6016
|
+
return coords[0];
|
|
6017
|
+
case "MultiLineString":
|
|
6018
|
+
case "Polygon":
|
|
6019
|
+
return coords[0][0];
|
|
6020
|
+
case "MultiPolygon":
|
|
6021
|
+
return coords[0][0][0];
|
|
6022
|
+
default:
|
|
6023
|
+
return [0, 0];
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
function pickName(props, fallback) {
|
|
6027
|
+
if (!props)
|
|
6028
|
+
return fallback;
|
|
6029
|
+
for (const key of ["plannavn", "bezeichnung", "name", "strassenname", "objektname", "title", "label"]) {
|
|
6030
|
+
if (typeof props[key] === "string" && props[key])
|
|
6031
|
+
return props[key];
|
|
6032
|
+
}
|
|
6033
|
+
return fallback;
|
|
6034
|
+
}
|
|
6035
|
+
function toLayerId2(sourceId, typeName, cqlFilter) {
|
|
6036
|
+
if (!cqlFilter)
|
|
6037
|
+
return `${sourceId}:${typeName}`;
|
|
6038
|
+
return `${sourceId}:${typeName}[${cqlFilter}]`;
|
|
6039
|
+
}
|
|
6040
|
+
var PRIORITY_CATEGORIES = /* @__PURE__ */ new Set(["building", "cadastre", "greenspace", "paved", "zone"]);
|
|
6041
|
+
function toLayerDescriptor2(sourceId, category, typeName, cqlFilter) {
|
|
6042
|
+
const shortName = typeName.replace(/^ms:/, "");
|
|
6043
|
+
const descriptor = featureCategoryDescriptor(category);
|
|
6044
|
+
const tier = PRIORITY_CATEGORIES.has(category) ? "priority" : "raw";
|
|
6045
|
+
return {
|
|
6046
|
+
id: toLayerId2(sourceId, typeName, cqlFilter),
|
|
6047
|
+
sourceId,
|
|
6048
|
+
sourceLayerId: typeName,
|
|
6049
|
+
category,
|
|
6050
|
+
preferredColor: descriptor.preferredColor,
|
|
6051
|
+
tier,
|
|
6052
|
+
label: shortName,
|
|
6053
|
+
description: `Z\xFCrich WFS layer ${shortName}`,
|
|
6054
|
+
labelKey: `gis.layer.${sourceId}.${shortName}.label`,
|
|
6055
|
+
descriptionKey: `gis.layer.${sourceId}.${shortName}.description`
|
|
6056
|
+
};
|
|
6057
|
+
}
|
|
6058
|
+
function toNormalisedBuildingProperties(props, sourceId) {
|
|
6059
|
+
if (!props)
|
|
6060
|
+
return { featureType: "building" };
|
|
6061
|
+
const rawArea = props["grundflaeche"] ?? props["gbf"] ?? props["gebaeudegrundrissflaeche"] ?? props["flaeche"] ?? void 0;
|
|
6062
|
+
const rawFloors = props["vollgeschosse"] ?? props["geschossanzahl"] ?? props["anzahl_geschosse"] ?? void 0;
|
|
6063
|
+
const buildingUse = props["gebaeudefunktion"] ?? props["gfkode"] ?? props["art"] ?? props["objektart"] ?? void 0;
|
|
6064
|
+
const rawYear = props["baujahr"] ?? props["bauperiode"] ?? void 0;
|
|
6065
|
+
const registryId = String(props["egid"] ?? props["gwr_egid"] ?? props["egris_egid"] ?? "").trim() || void 0;
|
|
6066
|
+
return {
|
|
6067
|
+
featureType: "building",
|
|
6068
|
+
areaM2: typeof rawArea === "number" ? rawArea : void 0,
|
|
6069
|
+
buildingUse: buildingUse ? String(buildingUse) : void 0,
|
|
6070
|
+
buildingUseGeneric: classifyBuildingUse(buildingUse, sourceId),
|
|
6071
|
+
floors: typeof rawFloors === "number" ? rawFloors : void 0,
|
|
6072
|
+
yearBuilt: typeof rawYear === "number" ? rawYear : void 0,
|
|
6073
|
+
registryId
|
|
6074
|
+
};
|
|
6075
|
+
}
|
|
6076
|
+
function toNormalisedPlotProperties(props, sourceId) {
|
|
6077
|
+
if (!props)
|
|
6078
|
+
return { featureType: "plot" };
|
|
6079
|
+
const registryId = props["egris_egrid"] ?? props["egrid"] ?? void 0;
|
|
6080
|
+
const nummer = props["nummer"] ?? void 0;
|
|
6081
|
+
const nbident = props["nbident"] ?? void 0;
|
|
6082
|
+
const plotId = nummer && nbident ? `${nummer}, ${nbident}` : nummer ?? void 0;
|
|
6083
|
+
const rawArea = props["flaechenmass"] ?? props["flaeche"] ?? void 0;
|
|
6084
|
+
const plotUse = props["art"] ?? props["nutzungsart"] ?? void 0;
|
|
6085
|
+
return {
|
|
6086
|
+
featureType: "plot",
|
|
6087
|
+
areaM2: typeof rawArea === "number" ? rawArea : void 0,
|
|
6088
|
+
plotUse: plotUse ? String(plotUse) : void 0,
|
|
6089
|
+
plotUseGeneric: classifyPlotUse(plotUse, sourceId),
|
|
6090
|
+
plotId,
|
|
6091
|
+
registryId
|
|
6092
|
+
};
|
|
6093
|
+
}
|
|
6094
|
+
function toNormalisedGreenspaceProperties(props) {
|
|
6095
|
+
if (!props)
|
|
6096
|
+
return { featureType: "greenspace" };
|
|
6097
|
+
const rawArea = props["flaeche"] ?? void 0;
|
|
6098
|
+
const surfaceType = props["art"] ?? void 0;
|
|
6099
|
+
return {
|
|
6100
|
+
featureType: "greenspace",
|
|
6101
|
+
areaM2: typeof rawArea === "number" ? rawArea : void 0,
|
|
6102
|
+
surfaceType: surfaceType ? String(surfaceType) : void 0
|
|
6103
|
+
};
|
|
6104
|
+
}
|
|
6105
|
+
function toNormalisedPavedProperties(props) {
|
|
6106
|
+
if (!props)
|
|
6107
|
+
return { featureType: "paved" };
|
|
6108
|
+
const rawArea = props["flaeche"] ?? void 0;
|
|
6109
|
+
const surfaceType = props["art"] ?? void 0;
|
|
6110
|
+
return {
|
|
6111
|
+
featureType: "paved",
|
|
6112
|
+
areaM2: typeof rawArea === "number" ? rawArea : void 0,
|
|
6113
|
+
surfaceType: surfaceType ? String(surfaceType) : void 0
|
|
6114
|
+
};
|
|
6115
|
+
}
|
|
6116
|
+
function toNormalisedZoneProperties(props) {
|
|
6117
|
+
if (!props)
|
|
6118
|
+
return { featureType: "zone" };
|
|
6119
|
+
const zoneType = props["plannavn"] ?? props["bezeichnung"] ?? props["typ_bezeichnung"] ?? props["art"] ?? void 0;
|
|
6120
|
+
const planId = props["planid"] ?? props["plannummer"] ?? void 0;
|
|
6121
|
+
const zoneCode = props["artcode"] ?? props["typ_code"] ?? props["abkuerzung"] ?? planId ?? void 0;
|
|
6122
|
+
const rawLegalStatus = props["rechtsstatus"] ?? props["status"] ?? void 0;
|
|
6123
|
+
const legalStatus = classifyZoneLegalStatus(rawLegalStatus);
|
|
6124
|
+
const rawPlanType = props["typ_gde_bezeichnung"] ?? void 0;
|
|
6125
|
+
const planType = classifyZonePlanType(rawPlanType);
|
|
6126
|
+
const planDocumentLink = props["dagsordenpunkt_url"] ?? props["dokument_url"] ?? props["link"] ?? void 0;
|
|
6127
|
+
const publicationDate = props["auflagedatum"] ?? void 0;
|
|
6128
|
+
const fixingDate = props["festsetzungsdatum"] ?? void 0;
|
|
6129
|
+
const approvalDate = props["genehmigungsdatum"] ?? void 0;
|
|
6130
|
+
const effectiveFrom = props["inkraftsetzungsdatum"] ?? void 0;
|
|
6131
|
+
const rawArea = props["flaeche"] ?? props["flaeche_m2"] ?? void 0;
|
|
6132
|
+
return {
|
|
6133
|
+
featureType: "zone",
|
|
6134
|
+
zoneType: zoneType ? String(zoneType) : void 0,
|
|
6135
|
+
zoneCode: zoneCode ? String(zoneCode) : void 0,
|
|
6136
|
+
legalStatus,
|
|
6137
|
+
planType,
|
|
6138
|
+
planId: planId ? String(planId) : void 0,
|
|
6139
|
+
planDocumentLink: planDocumentLink ? String(planDocumentLink) : void 0,
|
|
6140
|
+
publicationDate: publicationDate ?? void 0,
|
|
6141
|
+
fixingDate: fixingDate ?? void 0,
|
|
6142
|
+
approvalDate: approvalDate ?? void 0,
|
|
6143
|
+
effectiveFrom: effectiveFrom ?? void 0,
|
|
6144
|
+
areaM2: typeof rawArea === "number" ? rawArea : void 0
|
|
6145
|
+
};
|
|
6146
|
+
}
|
|
6147
|
+
function _makeCqlPostFilter(cqlFilter) {
|
|
6148
|
+
const eqMatch = cqlFilter.match(/^(\w+)\s*=\s*'([^']+)'$/);
|
|
6149
|
+
if (eqMatch) {
|
|
6150
|
+
const [, field, value] = eqMatch;
|
|
6151
|
+
return (props) => String(props?.[field] ?? "") === value;
|
|
6152
|
+
}
|
|
6153
|
+
const inMatch = cqlFilter.match(/^(\w+)\s+IN\s*\(([^)]+)\)$/i);
|
|
6154
|
+
if (inMatch) {
|
|
6155
|
+
const [, field, valuesStr] = inMatch;
|
|
6156
|
+
const allowed = new Set(
|
|
6157
|
+
valuesStr.split(",").map((v) => v.trim().replace(/^'|'$/g, ""))
|
|
6158
|
+
);
|
|
6159
|
+
return (props) => allowed.has(String(props?.[field] ?? ""));
|
|
6160
|
+
}
|
|
6161
|
+
return () => true;
|
|
6162
|
+
}
|
|
6163
|
+
var ZurichMapsAdapter = class {
|
|
6164
|
+
categoryMap;
|
|
6165
|
+
baseUrl;
|
|
6166
|
+
sourceId;
|
|
6167
|
+
outputFormat;
|
|
6168
|
+
constructor(options) {
|
|
6169
|
+
this.categoryMap = options.categoryMap;
|
|
6170
|
+
this.baseUrl = options.baseUrl ?? WFS_BASE_URL;
|
|
6171
|
+
this.sourceId = options.sourceId ?? ZURICH_SOURCE_ID;
|
|
6172
|
+
this.outputFormat = options.outputFormat ?? "application/json; subtype=geojson";
|
|
6173
|
+
}
|
|
6174
|
+
async listFeatureCategoryDescriptors(bbox) {
|
|
6175
|
+
const layers = await this.listAvailableLayers(bbox);
|
|
6176
|
+
return [...new Map(layers.map((layer) => [layer.category, featureCategoryDescriptor(layer.category)])).values()];
|
|
6177
|
+
}
|
|
6178
|
+
async listAvailableLayers(bbox) {
|
|
6179
|
+
const entries = Object.entries(this.categoryMap);
|
|
6180
|
+
const results = await Promise.allSettled(
|
|
6181
|
+
entries.flatMap(
|
|
6182
|
+
([category, layerEntries]) => layerEntries.map(async (entry) => {
|
|
6183
|
+
const typeName = entryTypeName(entry);
|
|
6184
|
+
const cqlFilter = entryCqlFilter(entry);
|
|
6185
|
+
const fc = await this._fetchFeatures(bbox, typeName, 1, cqlFilter);
|
|
6186
|
+
return { category, typeName, cqlFilter, hasResults: fc.features.length > 0 };
|
|
6187
|
+
})
|
|
6188
|
+
)
|
|
6189
|
+
);
|
|
6190
|
+
return results.filter(
|
|
6191
|
+
(r) => r.status === "fulfilled" && r.value.hasResults
|
|
6192
|
+
).map((r) => toLayerDescriptor2(this.sourceId, r.value.category, r.value.typeName, r.value.cqlFilter));
|
|
6193
|
+
}
|
|
6194
|
+
async getFeaturesForLayer(bbox, layerId) {
|
|
6195
|
+
const found = this._findLayerById(layerId);
|
|
6196
|
+
if (!found)
|
|
6197
|
+
return [];
|
|
6198
|
+
return this._fetchAndConvert(bbox, found.descriptor, found.cqlFilter);
|
|
6199
|
+
}
|
|
6200
|
+
async _fetchAndConvert(bbox, layer, cqlFilter) {
|
|
6201
|
+
const fc = await this._fetchFeatures(bbox, layer.sourceLayerId, void 0, cqlFilter);
|
|
6202
|
+
const postFilter = cqlFilter ? _makeCqlPostFilter(cqlFilter) : void 0;
|
|
6203
|
+
const features = postFilter ? fc.features.filter((f) => postFilter(f.properties)) : fc.features;
|
|
6204
|
+
return features.map((feature, index) => this.toGisFeature(feature, layer, index));
|
|
6205
|
+
}
|
|
6206
|
+
/**
|
|
6207
|
+
* Returns the categories that have at least one configured typename AND at
|
|
6208
|
+
* least one feature in the given bbox (light `count=1` probe per typename).
|
|
6209
|
+
*/
|
|
6210
|
+
async listFeatureCategories(bbox) {
|
|
6211
|
+
const layers = await this.listAvailableLayers(bbox);
|
|
6212
|
+
return [...new Set(layers.map((layer) => layer.category))];
|
|
6213
|
+
}
|
|
6214
|
+
async getFeaturesOfCategory(bbox, category) {
|
|
6215
|
+
const entries = this.categoryMap[category] ?? [];
|
|
6216
|
+
const batches = await Promise.all(
|
|
6217
|
+
entries.map((entry) => {
|
|
6218
|
+
const typeName = entryTypeName(entry);
|
|
6219
|
+
const cqlFilter = entryCqlFilter(entry);
|
|
6220
|
+
const layer = toLayerDescriptor2(this.sourceId, category, typeName);
|
|
6221
|
+
return this._fetchAndConvert(bbox, layer, cqlFilter);
|
|
6222
|
+
})
|
|
6223
|
+
);
|
|
6224
|
+
return batches.flat();
|
|
6225
|
+
}
|
|
6226
|
+
// ─── Private helpers ────────────────────────────────────────────────────────
|
|
6227
|
+
_findLayerById(layerId) {
|
|
6228
|
+
const entries = Object.entries(this.categoryMap);
|
|
6229
|
+
for (const [category, layerEntries] of entries) {
|
|
6230
|
+
for (const entry of layerEntries) {
|
|
6231
|
+
const typeName = entryTypeName(entry);
|
|
6232
|
+
const cqlFilter = entryCqlFilter(entry);
|
|
6233
|
+
const layer = toLayerDescriptor2(this.sourceId, category, typeName, cqlFilter);
|
|
6234
|
+
if (layer.id === layerId) {
|
|
6235
|
+
return { descriptor: layer, cqlFilter };
|
|
6236
|
+
}
|
|
6237
|
+
}
|
|
6238
|
+
}
|
|
6239
|
+
return void 0;
|
|
6240
|
+
}
|
|
6241
|
+
async _fetchFeatures(bbox, typeName, count, cqlFilter) {
|
|
6242
|
+
const [west, south, east, north] = bbox;
|
|
6243
|
+
const bboxParam = `${south},${west},${north},${east},urn:ogc:def:crs:EPSG::4326`;
|
|
6244
|
+
const params = new URLSearchParams({
|
|
6245
|
+
service: "WFS",
|
|
6246
|
+
version: "2.0.0",
|
|
6247
|
+
request: "GetFeature",
|
|
6248
|
+
typename: typeName,
|
|
6249
|
+
bbox: bboxParam,
|
|
6250
|
+
srsName: "EPSG:4326",
|
|
6251
|
+
outputFormat: this.outputFormat
|
|
6252
|
+
});
|
|
6253
|
+
if (count !== void 0) {
|
|
6254
|
+
params.set("count", String(count));
|
|
6255
|
+
}
|
|
6256
|
+
if (cqlFilter !== void 0) {
|
|
6257
|
+
params.set("CQL_FILTER", cqlFilter);
|
|
6258
|
+
}
|
|
6259
|
+
params.set("outputFormat", this.outputFormat);
|
|
6260
|
+
const url = `${this.baseUrl}?${params.toString()}`;
|
|
6261
|
+
const response = await fetch(url);
|
|
6262
|
+
if (!response.ok) {
|
|
6263
|
+
throw new Error(
|
|
6264
|
+
`WFS request failed for ${typeName} (${this.sourceId}): ${response.status} ${response.statusText}`
|
|
6265
|
+
);
|
|
6266
|
+
}
|
|
6267
|
+
return response.json();
|
|
6268
|
+
}
|
|
6269
|
+
toGisFeature(feature, layer, index) {
|
|
6270
|
+
const [lon, lat] = firstCoordinate(feature.geometry);
|
|
6271
|
+
const name = pickName(feature.properties, `${layer.sourceLayerId}[${index}]`);
|
|
6272
|
+
const featureBBox = feature.bbox ? [feature.bbox[0], feature.bbox[1], feature.bbox[2], feature.bbox[3]] : void 0;
|
|
6273
|
+
const tier = layer.tier;
|
|
6274
|
+
const properties = tier === "priority" ? this._extractNormalisedProperties(feature, layer) : void 0;
|
|
6275
|
+
return {
|
|
6276
|
+
id: feature.id ?? `${layer.sourceLayerId}/${index}`,
|
|
6277
|
+
preferredColor: layer.preferredColor,
|
|
6278
|
+
sourceId: this.sourceId,
|
|
6279
|
+
sourceFeatureId: feature.id ?? `${layer.sourceLayerId}/${index}`,
|
|
6280
|
+
layerId: layer.id,
|
|
6281
|
+
name,
|
|
6282
|
+
category: layer.category,
|
|
6283
|
+
type: layer.sourceLayerId.replace(/^ms:/, ""),
|
|
6284
|
+
lat,
|
|
6285
|
+
lon,
|
|
6286
|
+
displayName: name,
|
|
6287
|
+
bbox: featureBBox,
|
|
6288
|
+
geometry: feature.geometry ?? void 0,
|
|
6289
|
+
tier,
|
|
6290
|
+
properties,
|
|
6291
|
+
originalData: feature.properties ?? {}
|
|
6292
|
+
};
|
|
6293
|
+
}
|
|
6294
|
+
_extractNormalisedProperties(feature, layer) {
|
|
6295
|
+
if (layer.category === "building") {
|
|
6296
|
+
return toNormalisedBuildingProperties(feature.properties, this.sourceId);
|
|
6297
|
+
}
|
|
6298
|
+
if (layer.category === "greenspace") {
|
|
6299
|
+
return toNormalisedGreenspaceProperties(feature.properties);
|
|
6300
|
+
}
|
|
6301
|
+
if (layer.category === "paved") {
|
|
6302
|
+
return toNormalisedPavedProperties(feature.properties);
|
|
6303
|
+
}
|
|
6304
|
+
if (layer.category === "zone") {
|
|
6305
|
+
return toNormalisedZoneProperties(feature.properties);
|
|
6306
|
+
}
|
|
6307
|
+
return toNormalisedPlotProperties(feature.properties, this.sourceId);
|
|
6308
|
+
}
|
|
6309
|
+
};
|
|
6310
|
+
|
|
6311
|
+
// libs/js/cue-gis/src/lib/danish-cadastre-adapter.ts
|
|
6312
|
+
var DENMARK_BBOX = [8, 54.5, 15.2, 57.8];
|
|
6313
|
+
|
|
6314
|
+
// libs/js/cue-gis/src/lib/cue-sdk-gis-adapter.ts
|
|
6315
|
+
var DEFAULT_DANISH_SDK_CATEGORY_MAP = {
|
|
6316
|
+
building: [
|
|
6317
|
+
{ source: "danish-matrikel", typename: "bbr_v001:bygning_current" }
|
|
6318
|
+
],
|
|
6319
|
+
cadastre: [
|
|
6320
|
+
{ source: "danish-matrikel", typename: "mat_v001:lodflade_current" },
|
|
6321
|
+
{ source: "danish-matrikel", typename: "mat_v001:samletfastejendom_current" }
|
|
6322
|
+
]
|
|
6323
|
+
};
|
|
6324
|
+
function firstCoordinate2(geometry) {
|
|
6325
|
+
if (!geometry)
|
|
6326
|
+
return [0, 0];
|
|
6327
|
+
const coords = geometry.coordinates;
|
|
6328
|
+
switch (geometry.type) {
|
|
6329
|
+
case "Point":
|
|
6330
|
+
return coords;
|
|
6331
|
+
case "MultiPoint":
|
|
6332
|
+
case "LineString":
|
|
6333
|
+
return coords[0];
|
|
6334
|
+
case "MultiLineString":
|
|
6335
|
+
case "Polygon":
|
|
6336
|
+
return coords[0][0];
|
|
6337
|
+
case "MultiPolygon":
|
|
6338
|
+
return coords[0][0][0];
|
|
6339
|
+
default:
|
|
6340
|
+
return [0, 0];
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
function pickName2(props, fallback) {
|
|
6344
|
+
if (!props)
|
|
6345
|
+
return fallback;
|
|
6346
|
+
for (const key of [
|
|
6347
|
+
"mat:matrikelnummer",
|
|
6348
|
+
"matrikelnummer",
|
|
6349
|
+
"mat:ejerlavsnavn",
|
|
6350
|
+
"ejerlavsnavn",
|
|
6351
|
+
"name",
|
|
6352
|
+
"bezeichnung",
|
|
6353
|
+
"stednavntekst",
|
|
6354
|
+
"title",
|
|
6355
|
+
"label"
|
|
6356
|
+
]) {
|
|
6357
|
+
if (typeof props[key] === "string" && props[key])
|
|
6358
|
+
return props[key];
|
|
6359
|
+
}
|
|
6360
|
+
return fallback;
|
|
6361
|
+
}
|
|
6362
|
+
function toLayerId3(sourceId, source, typename) {
|
|
6363
|
+
return `${sourceId}:${source}:${typename}`;
|
|
6364
|
+
}
|
|
6365
|
+
var PRIORITY_CATEGORIES2 = /* @__PURE__ */ new Set(["building", "cadastre", "zone"]);
|
|
6366
|
+
function toLayerDescriptor3(sourceId, category, entry) {
|
|
6367
|
+
const shortName = entry.typename.replace(/^[^:]+:/, "");
|
|
6368
|
+
const descriptor = featureCategoryDescriptor(category);
|
|
6369
|
+
const tier = PRIORITY_CATEGORIES2.has(category) ? "priority" : "raw";
|
|
6370
|
+
return {
|
|
6371
|
+
id: toLayerId3(sourceId, entry.source, entry.typename),
|
|
6372
|
+
sourceId,
|
|
6373
|
+
sourceLayerId: entry.typename,
|
|
6374
|
+
category,
|
|
6375
|
+
preferredColor: descriptor.preferredColor,
|
|
6376
|
+
tier,
|
|
6377
|
+
label: entry.label ?? shortName,
|
|
6378
|
+
description: `${entry.source} / ${entry.typename}`,
|
|
6379
|
+
labelKey: `gis.layer.${sourceId}.${entry.source}.${shortName}.label`,
|
|
6380
|
+
descriptionKey: `gis.layer.${sourceId}.${entry.source}.${shortName}.description`
|
|
6381
|
+
};
|
|
6382
|
+
}
|
|
6383
|
+
function _extractNormalizedProperties(props, category) {
|
|
6384
|
+
if (category === "building") {
|
|
6385
|
+
const rawArea2 = props?.["byg041BebyggetAreal"] ?? props?.["byg038SamletBygningsareal"];
|
|
6386
|
+
const buildingUse = props?.["byg021BygningensAnvendelse"];
|
|
6387
|
+
const registryId = props?.["id_lokalId"];
|
|
6388
|
+
const rawYear = props?.["byg026Opf\xF8relses\xE5r"];
|
|
6389
|
+
return {
|
|
6390
|
+
featureType: "building",
|
|
6391
|
+
areaM2: typeof rawArea2 === "number" ? rawArea2 : void 0,
|
|
6392
|
+
buildingUse: buildingUse ?? void 0,
|
|
6393
|
+
yearBuilt: typeof rawYear === "number" ? rawYear : void 0,
|
|
6394
|
+
registryId: registryId ?? void 0
|
|
6395
|
+
};
|
|
6396
|
+
}
|
|
6397
|
+
if (category === "zone") {
|
|
6398
|
+
const zoneType = props?.["plannavn"] ?? props?.["bezeichnung"] ?? props?.["typ_bezeichnung"] ?? void 0;
|
|
6399
|
+
const planId = props?.["planid"] ?? props?.["plannummer"] ?? void 0;
|
|
6400
|
+
const zoneCode = props?.["artcode"] ?? props?.["typ_code"] ?? planId ?? void 0;
|
|
6401
|
+
const rawLegalStatus = props?.["rechtsstatus"] ?? props?.["status"] ?? void 0;
|
|
6402
|
+
const planDocumentLink = props?.["dagsordenpunkt_url"] ?? props?.["dokument_url"] ?? void 0;
|
|
6403
|
+
const rawArea2 = props?.["flaeche"] ?? props?.["flaeche_m2"] ?? void 0;
|
|
6404
|
+
return {
|
|
6405
|
+
featureType: "zone",
|
|
6406
|
+
zoneType: zoneType ?? void 0,
|
|
6407
|
+
zoneCode: zoneCode ?? void 0,
|
|
6408
|
+
legalStatus: classifyZoneLegalStatus(rawLegalStatus),
|
|
6409
|
+
planType: classifyZonePlanType(props?.["typ_gde_bezeichnung"]),
|
|
6410
|
+
planId: planId ?? void 0,
|
|
6411
|
+
planDocumentLink: planDocumentLink ?? void 0,
|
|
6412
|
+
publicationDate: props?.["auflagedatum"] ?? void 0,
|
|
6413
|
+
fixingDate: props?.["festsetzungsdatum"] ?? void 0,
|
|
6414
|
+
approvalDate: props?.["genehmigungsdatum"] ?? void 0,
|
|
6415
|
+
effectiveFrom: props?.["inkraftsetzungsdatum"] ?? void 0,
|
|
6416
|
+
areaM2: typeof rawArea2 === "number" ? rawArea2 : void 0
|
|
6417
|
+
};
|
|
6418
|
+
}
|
|
6419
|
+
const parcelNr = props?.["mat:matrikelnummer"] ?? props?.["matrikelnummer"];
|
|
6420
|
+
const districtName = props?.["mat:ejerlavsnavn"] ?? props?.["ejerlavsnavn"];
|
|
6421
|
+
const rawArea = props?.["mat:registreretareal"] ?? props?.["registreretareal"];
|
|
6422
|
+
const bfeNr = props?.["mat:bfenummer"] ?? props?.["bfenummer"];
|
|
6423
|
+
return {
|
|
6424
|
+
featureType: "plot",
|
|
6425
|
+
areaM2: typeof rawArea === "number" ? rawArea : void 0,
|
|
6426
|
+
plotId: parcelNr && districtName ? `${parcelNr}, ${districtName}` : parcelNr ?? void 0,
|
|
6427
|
+
registryId: bfeNr ?? void 0
|
|
6428
|
+
};
|
|
6429
|
+
}
|
|
6430
|
+
var CueSdkGisAdapter = class {
|
|
6431
|
+
categoryMap;
|
|
6432
|
+
dataViewsBaseUrl;
|
|
6433
|
+
getHeaders;
|
|
6434
|
+
sourceId;
|
|
6435
|
+
constructor(options) {
|
|
6436
|
+
this.dataViewsBaseUrl = options.dataViewsBaseUrl.replace(/\/$/, "");
|
|
6437
|
+
this.getHeaders = options.getHeaders;
|
|
6438
|
+
this.categoryMap = options.categoryMap ?? DEFAULT_DANISH_SDK_CATEGORY_MAP;
|
|
6439
|
+
this.sourceId = options.sourceId ?? "cue-sdk-gis";
|
|
6440
|
+
}
|
|
6441
|
+
async listFeatureCategoryDescriptors(bbox) {
|
|
6442
|
+
const layers = await this.listAvailableLayers(bbox);
|
|
6443
|
+
return [
|
|
6444
|
+
...new Map(
|
|
6445
|
+
layers.map((l) => [l.category, featureCategoryDescriptor(l.category)])
|
|
6446
|
+
).values()
|
|
6447
|
+
];
|
|
6448
|
+
}
|
|
6449
|
+
async listAvailableLayers(bbox) {
|
|
6450
|
+
const entries = Object.entries(this.categoryMap);
|
|
6451
|
+
const results = await Promise.allSettled(
|
|
6452
|
+
entries.flatMap(
|
|
6453
|
+
([category, layerEntries]) => layerEntries.map(async (entry) => {
|
|
6454
|
+
const fc = await this._fetchFeatures(bbox, entry, 1);
|
|
6455
|
+
return { category, entry, hasResults: fc.features.length > 0 };
|
|
6456
|
+
})
|
|
6457
|
+
)
|
|
6458
|
+
);
|
|
6459
|
+
return results.filter(
|
|
6460
|
+
(r) => r.status === "fulfilled" && r.value.hasResults
|
|
6461
|
+
).map((r) => toLayerDescriptor3(this.sourceId, r.value.category, r.value.entry));
|
|
6462
|
+
}
|
|
6463
|
+
async getFeaturesForLayer(bbox, layerId) {
|
|
6464
|
+
const result = this._findLayerById(layerId);
|
|
6465
|
+
if (!result)
|
|
6466
|
+
return [];
|
|
6467
|
+
const fc = await this._fetchFeatures(bbox, result.entry);
|
|
6468
|
+
return fc.features.map((f, i) => this._toGisFeature(f, result.descriptor, i));
|
|
6469
|
+
}
|
|
6470
|
+
async listFeatureCategories(bbox) {
|
|
6471
|
+
const layers = await this.listAvailableLayers(bbox);
|
|
6472
|
+
return [...new Set(layers.map((l) => l.category))];
|
|
6473
|
+
}
|
|
6474
|
+
async getFeaturesOfCategory(bbox, category) {
|
|
6475
|
+
const layerEntries = this.categoryMap[category] ?? [];
|
|
6476
|
+
const batches = await Promise.all(
|
|
6477
|
+
layerEntries.map((entry) => {
|
|
6478
|
+
const descriptor = toLayerDescriptor3(this.sourceId, category, entry);
|
|
6479
|
+
return this.getFeaturesForLayer(bbox, descriptor.id);
|
|
6480
|
+
})
|
|
6481
|
+
);
|
|
6482
|
+
return batches.flat();
|
|
6483
|
+
}
|
|
6484
|
+
// ─── Private helpers ────────────────────────────────────────────────────────
|
|
6485
|
+
_findLayerById(layerId) {
|
|
6486
|
+
for (const [category, entries] of Object.entries(this.categoryMap)) {
|
|
6487
|
+
for (const entry of entries) {
|
|
6488
|
+
const descriptor = toLayerDescriptor3(this.sourceId, category, entry);
|
|
6489
|
+
if (descriptor.id === layerId)
|
|
6490
|
+
return { entry, descriptor };
|
|
6491
|
+
}
|
|
6492
|
+
}
|
|
6493
|
+
return void 0;
|
|
6494
|
+
}
|
|
6495
|
+
async _fetchFeatures(bbox, entry, count) {
|
|
6496
|
+
const [west, south, east, north] = bbox;
|
|
6497
|
+
const headers = await this.getHeaders();
|
|
6498
|
+
const params = new URLSearchParams({
|
|
6499
|
+
source: entry.source,
|
|
6500
|
+
typename: entry.typename,
|
|
6501
|
+
bbox: `${west},${south},${east},${north}`
|
|
6502
|
+
});
|
|
6503
|
+
if (count !== void 0) {
|
|
6504
|
+
params.set("count", String(count));
|
|
6505
|
+
}
|
|
6506
|
+
const url = `${this.dataViewsBaseUrl}/gis/features?${params.toString()}`;
|
|
6507
|
+
const response = await fetch(url, { headers });
|
|
6508
|
+
if (!response.ok) {
|
|
6509
|
+
throw new Error(
|
|
6510
|
+
`CueSdkGisAdapter: request failed for ${entry.source}/${entry.typename}: ${response.status} ${response.statusText}`
|
|
6511
|
+
);
|
|
6512
|
+
}
|
|
6513
|
+
return response.json();
|
|
6514
|
+
}
|
|
6515
|
+
_toGisFeature(feature, layer, index) {
|
|
6516
|
+
const [lon, lat] = firstCoordinate2(feature.geometry);
|
|
6517
|
+
const featureId = feature.id ?? `${layer.sourceLayerId}/${index}`;
|
|
6518
|
+
const name = pickName2(feature.properties, featureId);
|
|
6519
|
+
const featureBBox = feature.bbox ? [feature.bbox[0], feature.bbox[1], feature.bbox[2], feature.bbox[3]] : void 0;
|
|
6520
|
+
const tier = layer.tier;
|
|
6521
|
+
const properties = tier === "priority" ? _extractNormalizedProperties(feature.properties, layer.category) : void 0;
|
|
6522
|
+
return {
|
|
6523
|
+
id: featureId,
|
|
6524
|
+
sourceId: this.sourceId,
|
|
6525
|
+
sourceFeatureId: featureId,
|
|
6526
|
+
layerId: layer.id,
|
|
6527
|
+
name,
|
|
6528
|
+
category: layer.category,
|
|
6529
|
+
preferredColor: layer.preferredColor,
|
|
6530
|
+
type: layer.sourceLayerId.replace(/^[^:]+:/, ""),
|
|
6531
|
+
lat,
|
|
6532
|
+
lon,
|
|
6533
|
+
displayName: name,
|
|
6534
|
+
bbox: featureBBox,
|
|
6535
|
+
geometry: feature.geometry ?? void 0,
|
|
6536
|
+
tier,
|
|
6537
|
+
properties,
|
|
6538
|
+
originalData: feature.properties ?? {}
|
|
6539
|
+
};
|
|
6540
|
+
}
|
|
6541
|
+
};
|
|
6542
|
+
|
|
6543
|
+
// libs/js/cue-gis/src/lib/global-config.ts
|
|
6544
|
+
var DEFAULT_ZURICH_CATEGORY_MAP = {
|
|
6545
|
+
address: [
|
|
6546
|
+
"ms:ogd-0406_arv_basis_avzh_hausnummer_pos_p",
|
|
6547
|
+
"ms:ogd-0571_afv_gv_strat_strassennetz_l"
|
|
6548
|
+
],
|
|
6549
|
+
poi: [
|
|
6550
|
+
"ms:ogd-0053_giszhpub_ogd_veloparkieranlagen_p",
|
|
6551
|
+
"ms:ogd-0406_arv_basis_avzh_gebaeudeeingang_p"
|
|
6552
|
+
],
|
|
6553
|
+
railway: [
|
|
6554
|
+
"ms:ogd-0529_afv_gv_seilbahn_skilift_l",
|
|
6555
|
+
"ms:ogd-0529_afv_gv_seilbahn_skilift_p"
|
|
6556
|
+
],
|
|
6557
|
+
natural: [
|
|
6558
|
+
"ms:ogd-0045_giszhpub_wb_fliessgewaesser_l",
|
|
6559
|
+
"ms:ogd-0045_giszhpub_wb_stehgewaesser_f",
|
|
6560
|
+
"ms:ogd-0111_giszhpub_wald_waldareal_f"
|
|
6561
|
+
],
|
|
6562
|
+
manmade: [
|
|
6563
|
+
"ms:ogd-0248_giszhpub_en_gebaeude_volumen_ha_f",
|
|
6564
|
+
// Transport / infrastructure surface types from the cantonal land-cover layer.
|
|
6565
|
+
{ typeName: "ms:ogd-0401_arv_basis_avzh_bodenbedeckung_f", cqlFilter: "art IN ('Bahngebiet', 'Strasse_Weg', 'Flugplatz', 'Bruecke')" }
|
|
6566
|
+
],
|
|
6567
|
+
building: [
|
|
6568
|
+
// Individual building footprint polygons from the official cantonal survey (AV ZH).
|
|
6569
|
+
// BoFlaeche covers all land-cover types; the CQL filter restricts to buildings.
|
|
6570
|
+
{ typeName: "ms:ogd-0401_arv_basis_avzh_bodenbedeckung_f", cqlFilter: "art = 'Geb\xE4ude'" }
|
|
6571
|
+
],
|
|
6572
|
+
greenspace: [
|
|
6573
|
+
// Parks, gardens, playgrounds, sports facilities and other vegetated open areas.
|
|
6574
|
+
{ typeName: "ms:ogd-0401_arv_basis_avzh_bodenbedeckung_f", cqlFilter: "art IN ('Gartenanlage', 'Parkanlage', 'Spielplatz', 'Sportanlage', 'Friedhof', 'Gehoelz', 'Feuchtgebiet')" }
|
|
6575
|
+
],
|
|
6576
|
+
paved: [
|
|
6577
|
+
// Sealed / paved ground surfaces (courtyards, sidewalks, parking, etc.).
|
|
6578
|
+
{ typeName: "ms:ogd-0401_arv_basis_avzh_bodenbedeckung_f", cqlFilter: "art IN ('befBoden', 'befestigte Fl\xE4che', 'Hof', 'Trottoir', 'Parkplatz')" }
|
|
6579
|
+
],
|
|
6580
|
+
cadastre: [
|
|
6581
|
+
// Land parcels (Liegenschaften) from the official cantonal survey (AV ZH)
|
|
6582
|
+
"ms:ogd-0404_arv_basis_avzh_liegenschaften_f",
|
|
6583
|
+
// Independent and permanent rights (SelbstRecht / SDR)
|
|
6584
|
+
"ms:ogd-0404_arv_basis_avzh_selbstrecht_f"
|
|
6585
|
+
],
|
|
6586
|
+
zone: [
|
|
6587
|
+
// ÖREB Nutzungsplanung (Grundnutzung) – primary land-use zoning polygons
|
|
6588
|
+
"ms:ogd-0156_arv_basis_np_gn_zonenflaeche_f",
|
|
6589
|
+
// ÖREB Überlagernde Festlegungen (Flächen) – overlaying planning constraints
|
|
6590
|
+
"ms:ogd-0155_arv_basis_np_ul_flaeche_f"
|
|
6591
|
+
]
|
|
6592
|
+
};
|
|
6593
|
+
var DEFAULT_LOCAL_SERVICE_REGIONS = [
|
|
6594
|
+
{
|
|
6595
|
+
name: "Canton of Z\xFCrich",
|
|
6596
|
+
coverageBBox: ZURICH_CANTON_BBOX,
|
|
6597
|
+
supportedCategories: ["address", "poi", "railway", "natural", "manmade", "building", "cadastre", "greenspace", "paved", "zone"],
|
|
6598
|
+
priority: 10,
|
|
6599
|
+
adapter: new ZurichMapsAdapter({
|
|
6600
|
+
categoryMap: DEFAULT_ZURICH_CATEGORY_MAP
|
|
6601
|
+
})
|
|
6602
|
+
},
|
|
6603
|
+
{
|
|
6604
|
+
name: "Switzerland (national AV cadastre)",
|
|
6605
|
+
coverageBBox: SWITZERLAND_BBOX,
|
|
6606
|
+
supportedCategories: ["cadastre"],
|
|
6607
|
+
priority: 5,
|
|
6608
|
+
// Lower priority than canton-specific adapters. For ZH, the canton adapter (priority 10) wins.
|
|
6609
|
+
adapter: new ZurichMapsAdapter({
|
|
6610
|
+
sourceId: "swiss-av-wfs",
|
|
6611
|
+
baseUrl: "https://wfs.geodienste.ch/av_0/deu",
|
|
6612
|
+
categoryMap: {
|
|
6613
|
+
cadastre: [
|
|
6614
|
+
"ms:RESF",
|
|
6615
|
+
// Rechtsgültige Liegenschaften (valid parcels, all cantons)
|
|
6616
|
+
"ms:DPRSF"
|
|
6617
|
+
// Selbständige und dauernde Rechte / SDR (all cantons)
|
|
6618
|
+
]
|
|
6619
|
+
}
|
|
6620
|
+
})
|
|
6621
|
+
},
|
|
6622
|
+
{
|
|
6623
|
+
/**
|
|
6624
|
+
* Denmark planning zones (lokalplan + kommuneplanrammer) from plandata.dk.
|
|
6625
|
+
*
|
|
6626
|
+
* This is a fully open WFS service (no API key required) provided by
|
|
6627
|
+
* Plandata, the Danish national planning data register.
|
|
6628
|
+
*
|
|
6629
|
+
* - `pdk:lokalplan_vedtaget` – adopted local plans (lokalplaner)
|
|
6630
|
+
* - `pdk:kommuneplanramme_vedtaget` – adopted municipal plan frameworks
|
|
6631
|
+
*/
|
|
6632
|
+
name: "Denmark (Plandata.dk \u2013 planning zones)",
|
|
6633
|
+
coverageBBox: DENMARK_BBOX,
|
|
6634
|
+
supportedCategories: ["zone"],
|
|
6635
|
+
priority: 10,
|
|
6636
|
+
adapter: new ZurichMapsAdapter({
|
|
6637
|
+
sourceId: "danish-plandata",
|
|
6638
|
+
baseUrl: "https://wfs.plandata.dk/geoserver/pdk/wfs",
|
|
6639
|
+
outputFormat: "application/json",
|
|
6640
|
+
categoryMap: {
|
|
6641
|
+
zone: [
|
|
6642
|
+
"pdk:lokalplan_vedtaget",
|
|
6643
|
+
// Adopted local plans
|
|
6644
|
+
"pdk:kommuneplanramme_vedtaget"
|
|
6645
|
+
// Adopted municipal plan frameworks
|
|
6646
|
+
]
|
|
6647
|
+
}
|
|
6648
|
+
})
|
|
6649
|
+
}
|
|
6650
|
+
];
|
|
6651
|
+
|
|
6652
|
+
// libs/js/cue-gis/src/lib/gis-gateway.ts
|
|
6653
|
+
function sortByCanonicalOrder(descriptors) {
|
|
6654
|
+
return [...descriptors].sort(
|
|
6655
|
+
(a5, b) => FEATURE_CATEGORIES.indexOf(a5.category) - FEATURE_CATEGORIES.indexOf(b.category)
|
|
6656
|
+
);
|
|
6657
|
+
}
|
|
6658
|
+
var GisGateway = class {
|
|
6659
|
+
nominatim;
|
|
6660
|
+
regions;
|
|
6661
|
+
/**
|
|
6662
|
+
* Effective zone plan type colour map — {@link ZONE_PLAN_TYPE_COLORS} merged
|
|
6663
|
+
* with any overrides supplied via {@link GisGatewayOptions.zonePlanTypeColors}.
|
|
6664
|
+
*/
|
|
6665
|
+
zonePlanTypeColors;
|
|
6666
|
+
constructor(options = {}) {
|
|
6667
|
+
const {
|
|
6668
|
+
regions,
|
|
6669
|
+
useDefaultRegions = true,
|
|
6670
|
+
zonePlanTypeColors,
|
|
6671
|
+
...nominatimOptions
|
|
6672
|
+
} = options;
|
|
6673
|
+
this.nominatim = new NominatimAdapter(nominatimOptions);
|
|
6674
|
+
const defaultRegions = useDefaultRegions ? DEFAULT_LOCAL_SERVICE_REGIONS : [];
|
|
6675
|
+
this.regions = regions ?? defaultRegions;
|
|
6676
|
+
this.zonePlanTypeColors = { ...ZONE_PLAN_TYPE_COLORS, ...zonePlanTypeColors };
|
|
6677
|
+
}
|
|
6678
|
+
/**
|
|
6679
|
+
* Return the available feature categories together with UI metadata such as
|
|
6680
|
+
* preferred color.
|
|
6681
|
+
*
|
|
6682
|
+
* When the matching regions declare their {@link LocalServiceRegion.supportedCategories}
|
|
6683
|
+
* the result is built from those declarations without network probing.
|
|
6684
|
+
* Otherwise the winning adapter's own probing is used.
|
|
6685
|
+
*
|
|
6686
|
+
* @param bbox [west, south, east, north] in WGS-84 decimal degrees
|
|
6687
|
+
*/
|
|
6688
|
+
async listFeatureCategoryDescriptors(bbox) {
|
|
6689
|
+
const declaredRegions = this.regions.filter((r) => bboxIntersects(r.coverageBBox, bbox) && r.supportedCategories).sort((a5, b) => (b.priority ?? 0) - (a5.priority ?? 0));
|
|
6690
|
+
if (declaredRegions.length > 0) {
|
|
6691
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6692
|
+
const descriptors = [];
|
|
6693
|
+
for (const region of declaredRegions) {
|
|
6694
|
+
for (const cat of region.supportedCategories ?? []) {
|
|
6695
|
+
if (!seen.has(cat)) {
|
|
6696
|
+
seen.add(cat);
|
|
6697
|
+
descriptors.push(featureCategoryDescriptor(cat));
|
|
6698
|
+
}
|
|
6699
|
+
}
|
|
6700
|
+
}
|
|
6701
|
+
for (const cat of NOMINATIM_FEATURE_CATEGORIES) {
|
|
6702
|
+
if (!seen.has(cat)) {
|
|
6703
|
+
descriptors.push(featureCategoryDescriptor(cat));
|
|
6704
|
+
}
|
|
6705
|
+
}
|
|
6706
|
+
return sortByCanonicalOrder(descriptors);
|
|
6707
|
+
}
|
|
6708
|
+
const probed = await this._adapterFor(bbox).listFeatureCategoryDescriptors(bbox);
|
|
6709
|
+
return sortByCanonicalOrder(probed);
|
|
6710
|
+
}
|
|
6711
|
+
/**
|
|
6712
|
+
* Return the feature categories that have at least one result within the
|
|
6713
|
+
* given bounding box.
|
|
6714
|
+
*
|
|
6715
|
+
* @param bbox [west, south, east, north] in WGS-84 decimal degrees
|
|
6716
|
+
*/
|
|
6717
|
+
async listFeatureCategories(bbox) {
|
|
6718
|
+
const descriptors = await this.listFeatureCategoryDescriptors(bbox);
|
|
6719
|
+
return descriptors.map((d) => d.category);
|
|
6720
|
+
}
|
|
6721
|
+
/**
|
|
6722
|
+
* Return all available layers for the given bbox using a provider-independent
|
|
6723
|
+
* layer descriptor shape.
|
|
6724
|
+
*/
|
|
6725
|
+
async listAvailableLayers(bbox) {
|
|
6726
|
+
return this._adapterFor(bbox).listAvailableLayers(bbox);
|
|
6727
|
+
}
|
|
6728
|
+
/**
|
|
6729
|
+
* Stream GIS features of the given category from **all** services whose
|
|
6730
|
+
* coverage intersects the query bbox (plus the Nominatim global fallback).
|
|
6731
|
+
*
|
|
6732
|
+
* Processing steps:
|
|
6733
|
+
* 1. Log all configured services and their coverage bboxes.
|
|
6734
|
+
* 2. Identify services whose `coverageBBox` intersects `bbox`.
|
|
6735
|
+
* 3. Log the matched services.
|
|
6736
|
+
* 4. Fan out requests to all matched services in parallel; yield features as
|
|
6737
|
+
* each source resolves (streaming).
|
|
6738
|
+
* 5. All features are already normalised to {@link GisFeature} by each adapter.
|
|
6739
|
+
* 6. Quality-based deduplication: when two sources return a feature for the
|
|
6740
|
+
* same real-world object, only the higher-quality geometry is emitted.
|
|
6741
|
+
* A polygon/multipolygon beats a linestring, which beats a point. If a
|
|
6742
|
+
* better feature arrives after a lower-quality one was already yielded,
|
|
6743
|
+
* the better feature is yielded again (same {@link GisFeature.id}) so the
|
|
6744
|
+
* consumer can replace its copy.
|
|
6745
|
+
*
|
|
6746
|
+
* @param bbox [west, south, east, north] in WGS-84 decimal degrees
|
|
6747
|
+
* @param category One of the categories returned by {@link listFeatureCategories}
|
|
6748
|
+
*/
|
|
6749
|
+
async *streamFeaturesOfCategory(bbox, category) {
|
|
6750
|
+
console.log("[GisGateway] Available services:");
|
|
6751
|
+
for (const region of this.regions) {
|
|
6752
|
+
const [w, s, e, n] = region.coverageBBox;
|
|
6753
|
+
console.log(
|
|
6754
|
+
` \u2022 ${region.name} bbox=[${w}, ${s}, ${e}, ${n}] priority=${region.priority ?? 0}`
|
|
6755
|
+
);
|
|
6756
|
+
}
|
|
6757
|
+
console.log(" \u2022 Nominatim (OSM) bbox=[global] priority=-1");
|
|
6758
|
+
const matchedRegions = this.regions.filter(
|
|
6759
|
+
(r) => bboxIntersects(r.coverageBBox, bbox) && (!r.supportedCategories || r.supportedCategories.includes(category))
|
|
6760
|
+
);
|
|
6761
|
+
const sources = [
|
|
6762
|
+
...matchedRegions.map((r) => ({
|
|
6763
|
+
name: r.name,
|
|
6764
|
+
adapter: r.adapter,
|
|
6765
|
+
priority: r.priority ?? 0
|
|
6766
|
+
})),
|
|
6767
|
+
{ name: "Nominatim (OSM)", adapter: this.nominatim, priority: -1 }
|
|
6768
|
+
];
|
|
6769
|
+
sources.sort((a5, b) => b.priority - a5.priority);
|
|
6770
|
+
console.log(`[GisGateway] Matched services for category "${category}":`);
|
|
6771
|
+
for (const s of sources) {
|
|
6772
|
+
console.log(` \u2022 ${s.name} (priority=${s.priority})`);
|
|
6773
|
+
}
|
|
6774
|
+
const qualityMap = /* @__PURE__ */ new Map();
|
|
6775
|
+
const remaining = new Map(
|
|
6776
|
+
sources.map(
|
|
6777
|
+
(s, i) => [
|
|
6778
|
+
i,
|
|
6779
|
+
s.adapter.getFeaturesOfCategory(bbox, category).then((features) => ({ features, idx: i })).catch((err) => {
|
|
6780
|
+
console.warn(`[GisGateway] Source "${s.name}" failed:`, err);
|
|
6781
|
+
return { features: [], idx: i };
|
|
6782
|
+
})
|
|
6783
|
+
]
|
|
6784
|
+
)
|
|
6785
|
+
);
|
|
6786
|
+
while (remaining.size > 0) {
|
|
6787
|
+
const { features, idx } = await Promise.race(remaining.values());
|
|
6788
|
+
remaining.delete(idx);
|
|
6789
|
+
for (const feature of features) {
|
|
6790
|
+
const key = _dedupKey(feature);
|
|
6791
|
+
const score = _geometryQuality(feature);
|
|
6792
|
+
const best = qualityMap.get(key);
|
|
6793
|
+
if (best === void 0 || score > best) {
|
|
6794
|
+
qualityMap.set(key, score);
|
|
6795
|
+
yield feature;
|
|
6796
|
+
}
|
|
6797
|
+
}
|
|
6798
|
+
}
|
|
6799
|
+
}
|
|
6800
|
+
/**
|
|
6801
|
+
* Return all GIS features of the given category within the bounding box.
|
|
6802
|
+
*
|
|
6803
|
+
* Fans out to all matching services in parallel, then applies:
|
|
6804
|
+
* 1. Spatial merging – Nominatim point features that fall within ~100 m of a
|
|
6805
|
+
* local-service polygon are merged into that polygon (enriching its
|
|
6806
|
+
* `originalData`) rather than returned as a separate feature.
|
|
6807
|
+
* 2. Quality-based deduplication – when two unmerged features represent the
|
|
6808
|
+
* same object, the one with richer geometry is kept.
|
|
6809
|
+
*
|
|
6810
|
+
* @param bbox [west, south, east, north] in WGS-84 decimal degrees
|
|
6811
|
+
* @param category One of the categories returned by {@link listFeatureCategories}
|
|
6812
|
+
*/
|
|
6813
|
+
async getFeaturesOfCategory(bbox, category) {
|
|
6814
|
+
const matchedRegions = this.regions.filter(
|
|
6815
|
+
(r) => bboxIntersects(r.coverageBBox, bbox) && (!r.supportedCategories || r.supportedCategories.includes(category))
|
|
6816
|
+
);
|
|
6817
|
+
const sources = [
|
|
6818
|
+
...matchedRegions.map((r) => ({ name: r.name, adapter: r.adapter, isNominatim: false })),
|
|
6819
|
+
{ name: "Nominatim (OSM)", adapter: this.nominatim, isNominatim: true }
|
|
6820
|
+
];
|
|
6821
|
+
const batches = await Promise.allSettled(
|
|
6822
|
+
sources.map(
|
|
6823
|
+
(s) => s.adapter.getFeaturesOfCategory(bbox, category).then((features) => ({
|
|
6824
|
+
features,
|
|
6825
|
+
isNominatim: s.isNominatim
|
|
6826
|
+
})).catch((err) => {
|
|
6827
|
+
console.warn(`[GisGateway] Source "${s.name}" failed:`, err);
|
|
6828
|
+
return { features: [], isNominatim: s.isNominatim };
|
|
6829
|
+
})
|
|
6830
|
+
)
|
|
6831
|
+
);
|
|
6832
|
+
const localFeatures = [];
|
|
6833
|
+
const nominatimFeatures = [];
|
|
6834
|
+
for (const result of batches) {
|
|
6835
|
+
if (result.status !== "fulfilled")
|
|
6836
|
+
continue;
|
|
6837
|
+
if (result.value.isNominatim) {
|
|
6838
|
+
nominatimFeatures.push(...result.value.features);
|
|
6839
|
+
} else {
|
|
6840
|
+
localFeatures.push(...result.value.features);
|
|
6841
|
+
}
|
|
6842
|
+
}
|
|
6843
|
+
const merged = _mergeNominatimIntoPolygons(localFeatures, nominatimFeatures);
|
|
6844
|
+
const qualityMap = /* @__PURE__ */ new Map();
|
|
6845
|
+
const deduplicated = [];
|
|
6846
|
+
for (const feature of merged) {
|
|
6847
|
+
const key = _dedupKey(feature);
|
|
6848
|
+
const score = _geometryQuality(feature);
|
|
6849
|
+
const best = qualityMap.get(key);
|
|
6850
|
+
if (best === void 0 || score > best) {
|
|
6851
|
+
qualityMap.set(key, score);
|
|
6852
|
+
deduplicated.push(feature);
|
|
6853
|
+
}
|
|
6854
|
+
}
|
|
6855
|
+
return deduplicated;
|
|
6856
|
+
}
|
|
6857
|
+
/** Fetch features for a specific layer descriptor id. */
|
|
6858
|
+
async getFeaturesForLayer(bbox, layerId) {
|
|
6859
|
+
return this._adapterFor(bbox).getFeaturesForLayer(bbox, layerId);
|
|
6860
|
+
}
|
|
6861
|
+
/** Resolve the best adapter for a query bbox (legacy single-adapter routing). */
|
|
6862
|
+
_adapterFor(bbox) {
|
|
6863
|
+
const region = this.regions.filter((r) => bboxIntersects(r.coverageBBox, bbox)).sort((a5, b) => (b.priority ?? 0) - (a5.priority ?? 0))[0];
|
|
6864
|
+
return region?.adapter ?? this.nominatim;
|
|
6865
|
+
}
|
|
6866
|
+
};
|
|
6867
|
+
function _geometryQuality(feature) {
|
|
6868
|
+
const gt = feature.geometry?.type;
|
|
6869
|
+
if (gt === "Polygon" || gt === "MultiPolygon")
|
|
6870
|
+
return 3;
|
|
6871
|
+
if (gt === "LineString" || gt === "MultiLineString")
|
|
6872
|
+
return 2;
|
|
6873
|
+
return 1;
|
|
6874
|
+
}
|
|
6875
|
+
function _dedupKey(feature) {
|
|
6876
|
+
const lat = Math.round(feature.lat * 1e3) / 1e3;
|
|
6877
|
+
const lon = Math.round(feature.lon * 1e3) / 1e3;
|
|
6878
|
+
const name = (feature.name ?? feature.id ?? "").toLowerCase().trim();
|
|
6879
|
+
return `${name}|${lat}|${lon}`;
|
|
6880
|
+
}
|
|
6881
|
+
var MERGE_THRESHOLD = 1e-3;
|
|
6882
|
+
function _latLonDist(a5, b) {
|
|
6883
|
+
const dlat = a5.lat - b.lat;
|
|
6884
|
+
const dlon = a5.lon - b.lon;
|
|
6885
|
+
return Math.sqrt(dlat * dlat + dlon * dlon);
|
|
6886
|
+
}
|
|
6887
|
+
function _mergeNominatimIntoPolygons(localFeatures, nominatimFeatures) {
|
|
6888
|
+
if (nominatimFeatures.length === 0)
|
|
6889
|
+
return localFeatures;
|
|
6890
|
+
const polygons = localFeatures.filter((f) => _geometryQuality(f) === 3);
|
|
6891
|
+
const nonPolygons = localFeatures.filter((f) => _geometryQuality(f) !== 3);
|
|
6892
|
+
const enriched = polygons.map((f) => ({ feature: { ...f, originalData: { ...f.originalData } }, matched: false }));
|
|
6893
|
+
const unmatched = [];
|
|
6894
|
+
for (const nom of nominatimFeatures) {
|
|
6895
|
+
let bestIdx = -1;
|
|
6896
|
+
let bestDist = MERGE_THRESHOLD;
|
|
6897
|
+
for (let i = 0; i < enriched.length; i++) {
|
|
6898
|
+
const dist = _latLonDist(nom, enriched[i].feature);
|
|
6899
|
+
if (dist < bestDist) {
|
|
6900
|
+
bestDist = dist;
|
|
6901
|
+
bestIdx = i;
|
|
6902
|
+
}
|
|
6903
|
+
}
|
|
6904
|
+
if (bestIdx >= 0) {
|
|
6905
|
+
enriched[bestIdx].feature.originalData = {
|
|
6906
|
+
...nom.originalData,
|
|
6907
|
+
...enriched[bestIdx].feature.originalData
|
|
6908
|
+
};
|
|
6909
|
+
enriched[bestIdx].matched = true;
|
|
6910
|
+
} else {
|
|
6911
|
+
unmatched.push(nom);
|
|
6912
|
+
}
|
|
6913
|
+
}
|
|
6914
|
+
return [
|
|
6915
|
+
...enriched.map((e) => e.feature),
|
|
6916
|
+
...nonPolygons,
|
|
6917
|
+
...unmatched
|
|
6918
|
+
];
|
|
6919
|
+
}
|
|
6920
|
+
|
|
6921
|
+
// libs/js/cue-sdk/src/lib/gis.ts
|
|
6922
|
+
var DATA_VIEWS_PATH = "/data-views";
|
|
6923
|
+
var DEBOUNCE_MS = 1500;
|
|
6924
|
+
var CueGis = class {
|
|
6925
|
+
// undefined = not yet built
|
|
6926
|
+
/** @internal — construct via `cue.gis`, not directly. */
|
|
6927
|
+
constructor(_getAuthHeaders, _gatewayUrl) {
|
|
6928
|
+
this._getAuthHeaders = _getAuthHeaders;
|
|
6929
|
+
this._gatewayUrl = _gatewayUrl;
|
|
6930
|
+
}
|
|
6931
|
+
_bbox = null;
|
|
6932
|
+
_projectId = null;
|
|
6933
|
+
_selectedCategories = /* @__PURE__ */ new Set();
|
|
6934
|
+
_queryToken = 0;
|
|
6935
|
+
_categoryTokens = /* @__PURE__ */ new Map();
|
|
6936
|
+
_debounceTimer = null;
|
|
6937
|
+
_availableCategories = [];
|
|
6938
|
+
_features = /* @__PURE__ */ new Map();
|
|
6939
|
+
_catListeners = /* @__PURE__ */ new Set();
|
|
6940
|
+
_featListeners = /* @__PURE__ */ new Set();
|
|
6941
|
+
_loadListeners = /* @__PURE__ */ new Set();
|
|
6942
|
+
_gatewayCache = null;
|
|
6943
|
+
_gatewayProjectId = void 0;
|
|
6944
|
+
// ── Setters ───────────────────────────────────────────────────────────────
|
|
6945
|
+
/**
|
|
6946
|
+
* Update the current map viewport.
|
|
6947
|
+
* Triggers a debounced query for available categories and reloads selected ones.
|
|
6948
|
+
*/
|
|
6949
|
+
setBbox(bbox) {
|
|
6950
|
+
this._bbox = bbox;
|
|
6951
|
+
this._scheduleDebouncedQuery();
|
|
6952
|
+
}
|
|
6953
|
+
/**
|
|
6954
|
+
* Set or clear the active project.
|
|
6955
|
+
* Rebuilds the authenticated adapter so subsequent requests carry the new project header.
|
|
6956
|
+
*/
|
|
6957
|
+
setProjectId(projectId) {
|
|
6958
|
+
if (projectId === this._projectId)
|
|
6959
|
+
return;
|
|
6960
|
+
this._projectId = projectId;
|
|
6961
|
+
this._gatewayCache = null;
|
|
6962
|
+
this._scheduleDebouncedQuery();
|
|
6963
|
+
}
|
|
6964
|
+
/**
|
|
6965
|
+
* Replace the full set of selected categories.
|
|
6966
|
+
* Newly added categories begin loading immediately; removed categories are cleared.
|
|
6967
|
+
*/
|
|
6968
|
+
setSelectedCategories(categories) {
|
|
6969
|
+
const prev = this._selectedCategories;
|
|
6970
|
+
this._selectedCategories = new Set(categories);
|
|
6971
|
+
let featuresChanged = false;
|
|
6972
|
+
for (const cat of prev) {
|
|
6973
|
+
if (!categories.has(cat)) {
|
|
6974
|
+
this._features.delete(cat);
|
|
6975
|
+
featuresChanged = true;
|
|
6976
|
+
}
|
|
6977
|
+
}
|
|
6978
|
+
if (featuresChanged)
|
|
6979
|
+
this._emitFeatures();
|
|
6980
|
+
const bbox = this._bbox;
|
|
6981
|
+
if (bbox) {
|
|
6982
|
+
for (const cat of categories) {
|
|
6983
|
+
if (!prev.has(cat))
|
|
6984
|
+
this._loadCategory(cat, bbox, this._queryToken);
|
|
6985
|
+
}
|
|
6986
|
+
}
|
|
6987
|
+
}
|
|
6988
|
+
// ── Subscriptions ─────────────────────────────────────────────────────────
|
|
6989
|
+
/**
|
|
6990
|
+
* Subscribe to available-category updates.
|
|
6991
|
+
* Replays the current value immediately, then fires on every bbox change.
|
|
6992
|
+
* @returns An unsubscribe function.
|
|
6993
|
+
*/
|
|
6994
|
+
onAvailableCategories(cb) {
|
|
6995
|
+
cb([...this._availableCategories]);
|
|
6996
|
+
this._catListeners.add(cb);
|
|
6997
|
+
return () => this._catListeners.delete(cb);
|
|
6998
|
+
}
|
|
6999
|
+
/**
|
|
7000
|
+
* Subscribe to the feature map (category → GisFeature[]).
|
|
7001
|
+
* Replays the current value immediately, then fires whenever features change.
|
|
7002
|
+
* @returns An unsubscribe function.
|
|
7003
|
+
*/
|
|
7004
|
+
onFeaturesChange(cb) {
|
|
7005
|
+
cb(new Map(this._features));
|
|
7006
|
+
this._featListeners.add(cb);
|
|
7007
|
+
return () => this._featListeners.delete(cb);
|
|
7008
|
+
}
|
|
7009
|
+
/**
|
|
7010
|
+
* Subscribe to the global loading state.
|
|
7011
|
+
* Fires `true` while the category list is being queried, `false` when done.
|
|
7012
|
+
* @returns An unsubscribe function.
|
|
7013
|
+
*/
|
|
7014
|
+
onLoadingChange(cb) {
|
|
7015
|
+
this._loadListeners.add(cb);
|
|
7016
|
+
return () => this._loadListeners.delete(cb);
|
|
7017
|
+
}
|
|
7018
|
+
/** Cancel all pending requests and clear all listeners. */
|
|
7019
|
+
destroy() {
|
|
7020
|
+
if (this._debounceTimer !== null)
|
|
7021
|
+
clearTimeout(this._debounceTimer);
|
|
7022
|
+
this._queryToken++;
|
|
7023
|
+
this._catListeners.clear();
|
|
7024
|
+
this._featListeners.clear();
|
|
7025
|
+
this._loadListeners.clear();
|
|
7026
|
+
}
|
|
7027
|
+
// ── Private ───────────────────────────────────────────────────────────────
|
|
7028
|
+
_scheduleDebouncedQuery() {
|
|
7029
|
+
if (this._debounceTimer !== null)
|
|
7030
|
+
clearTimeout(this._debounceTimer);
|
|
7031
|
+
this._debounceTimer = setTimeout(() => this._queryLayers(), DEBOUNCE_MS);
|
|
7032
|
+
}
|
|
7033
|
+
_getGateway() {
|
|
7034
|
+
if (this._gatewayCache !== null && this._gatewayProjectId === this._projectId) {
|
|
7035
|
+
return this._gatewayCache;
|
|
7036
|
+
}
|
|
7037
|
+
const projectId = this._projectId;
|
|
7038
|
+
const regions = [...DEFAULT_LOCAL_SERVICE_REGIONS];
|
|
7039
|
+
if (projectId) {
|
|
7040
|
+
regions.unshift({
|
|
7041
|
+
name: "Cue SDK (authenticated)",
|
|
7042
|
+
coverageBBox: DENMARK_BBOX,
|
|
7043
|
+
supportedCategories: Object.keys(DEFAULT_DANISH_SDK_CATEGORY_MAP),
|
|
7044
|
+
priority: 10,
|
|
7045
|
+
adapter: new CueSdkGisAdapter({
|
|
7046
|
+
dataViewsBaseUrl: `${this._gatewayUrl}${DATA_VIEWS_PATH}`,
|
|
7047
|
+
getHeaders: async () => ({
|
|
7048
|
+
...await this._getAuthHeaders(),
|
|
7049
|
+
"x-project-id": projectId
|
|
7050
|
+
})
|
|
7051
|
+
})
|
|
7052
|
+
});
|
|
7053
|
+
}
|
|
7054
|
+
this._gatewayCache = new GisGateway({ regions });
|
|
7055
|
+
this._gatewayProjectId = projectId;
|
|
7056
|
+
return this._gatewayCache;
|
|
7057
|
+
}
|
|
7058
|
+
async _queryLayers() {
|
|
7059
|
+
const bbox = this._bbox;
|
|
7060
|
+
if (!bbox)
|
|
7061
|
+
return;
|
|
7062
|
+
this._queryToken++;
|
|
7063
|
+
const token = this._queryToken;
|
|
7064
|
+
this._emitLoading(true);
|
|
7065
|
+
try {
|
|
7066
|
+
const descriptors = await this._getGateway().listFeatureCategoryDescriptors(bbox);
|
|
7067
|
+
if (token !== this._queryToken)
|
|
7068
|
+
return;
|
|
7069
|
+
this._availableCategories = descriptors;
|
|
7070
|
+
this._catListeners.forEach((cb) => cb([...descriptors]));
|
|
7071
|
+
const available = new Set(descriptors.map((d) => d.category));
|
|
7072
|
+
let changed = false;
|
|
7073
|
+
for (const cat of [...this._selectedCategories]) {
|
|
7074
|
+
if (!available.has(cat)) {
|
|
7075
|
+
this._selectedCategories.delete(cat);
|
|
7076
|
+
this._features.delete(cat);
|
|
7077
|
+
changed = true;
|
|
7078
|
+
}
|
|
7079
|
+
}
|
|
7080
|
+
if (changed)
|
|
7081
|
+
this._emitFeatures();
|
|
7082
|
+
for (const cat of this._selectedCategories) {
|
|
7083
|
+
this._loadCategory(cat, bbox, token);
|
|
7084
|
+
}
|
|
7085
|
+
} catch (err) {
|
|
7086
|
+
console.error("[CueGis] Failed to list categories:", err);
|
|
7087
|
+
} finally {
|
|
7088
|
+
if (token === this._queryToken)
|
|
7089
|
+
this._emitLoading(false);
|
|
7090
|
+
}
|
|
7091
|
+
}
|
|
7092
|
+
async _loadCategory(category, bbox, ownerToken) {
|
|
7093
|
+
const catToken = (this._categoryTokens.get(category) ?? 0) + 1;
|
|
7094
|
+
this._categoryTokens.set(category, catToken);
|
|
7095
|
+
try {
|
|
7096
|
+
const features = await this._getGateway().getFeaturesOfCategory(bbox, category);
|
|
7097
|
+
const stale = this._categoryTokens.get(category) !== catToken || ownerToken !== this._queryToken || !this._selectedCategories.has(category);
|
|
7098
|
+
if (stale)
|
|
7099
|
+
return;
|
|
7100
|
+
this._features.set(category, features);
|
|
7101
|
+
this._emitFeatures();
|
|
7102
|
+
} catch (err) {
|
|
7103
|
+
console.error(`[CueGis] Failed to load "${category}":`, err);
|
|
7104
|
+
}
|
|
7105
|
+
}
|
|
7106
|
+
_emitFeatures() {
|
|
7107
|
+
const snapshot = new Map(this._features);
|
|
7108
|
+
this._featListeners.forEach((cb) => cb(snapshot));
|
|
7109
|
+
}
|
|
7110
|
+
_emitLoading(v) {
|
|
7111
|
+
this._loadListeners.forEach((cb) => cb(v));
|
|
7112
|
+
}
|
|
7113
|
+
};
|
|
7114
|
+
|
|
5364
7115
|
// libs/js/cue-sdk/src/lib/project.ts
|
|
5365
7116
|
var import_firestore3 = require("firebase/firestore");
|
|
5366
7117
|
var import_functions2 = require("firebase/functions");
|
|
@@ -5549,7 +7300,7 @@ var CueProfile = class {
|
|
|
5549
7300
|
}
|
|
5550
7301
|
/** Creates a new API key for the current user. */
|
|
5551
7302
|
async createAPIKey(expiration) {
|
|
5552
|
-
return this._fetch(
|
|
7303
|
+
return this._fetch(ENDPOINT_COMMANDS_PROFILE_API_KEYS, {
|
|
5553
7304
|
method: "POST",
|
|
5554
7305
|
headers: { "Content-Type": "application/json" },
|
|
5555
7306
|
body: JSON.stringify({ expiration })
|
|
@@ -5558,7 +7309,7 @@ var CueProfile = class {
|
|
|
5558
7309
|
/** Revokes the current user's API key. */
|
|
5559
7310
|
async revokeAPIKey() {
|
|
5560
7311
|
const response = await this._auth.authenticatedFetch(
|
|
5561
|
-
this._url(
|
|
7312
|
+
this._url(ENDPOINT_COMMANDS_PROFILE_API_KEYS),
|
|
5562
7313
|
{ method: "DELETE" }
|
|
5563
7314
|
);
|
|
5564
7315
|
if (!response.ok) {
|
|
@@ -5595,10 +7346,26 @@ var CueProfile = class {
|
|
|
5595
7346
|
const res = await fn({ uids });
|
|
5596
7347
|
return res.data;
|
|
5597
7348
|
}
|
|
5598
|
-
/** Record that the current user has accepted the terms of service. */
|
|
5599
|
-
async acceptTerms() {
|
|
5600
|
-
|
|
5601
|
-
|
|
7349
|
+
/** Record that the current user has accepted the terms of service. Sets a `terms` custom claim on the token. */
|
|
7350
|
+
async acceptTerms(version) {
|
|
7351
|
+
await this._fetch(ENDPOINT_COMMANDS_PROFILE_TERMS, {
|
|
7352
|
+
method: "POST",
|
|
7353
|
+
headers: { "Content-Type": "application/json" },
|
|
7354
|
+
body: JSON.stringify({ version })
|
|
7355
|
+
});
|
|
7356
|
+
}
|
|
7357
|
+
/**
|
|
7358
|
+
* Returns the terms version the current user has accepted (e.g. `"v1"`),
|
|
7359
|
+
* or `null` if they have not accepted any version yet.
|
|
7360
|
+
* Reads from the cached ID token — call after `acceptTerms()` with a
|
|
7361
|
+
* force-refreshed token to see the updated value.
|
|
7362
|
+
*/
|
|
7363
|
+
async latestTermsAccepted() {
|
|
7364
|
+
const user = this._auth.currentUser;
|
|
7365
|
+
if (!user)
|
|
7366
|
+
return null;
|
|
7367
|
+
const result = await (0, import_auth4.getIdTokenResult)(user);
|
|
7368
|
+
return result.claims["terms"] ?? null;
|
|
5602
7369
|
}
|
|
5603
7370
|
};
|
|
5604
7371
|
|
|
@@ -7724,7 +9491,10 @@ function uploadedFileMetadata(originalName, projectId, userId, md5, providerId,
|
|
|
7724
9491
|
|
|
7725
9492
|
// libs/js/cue-sdk/src/lib/sync.ts
|
|
7726
9493
|
async function _fs2() {
|
|
7727
|
-
return import(
|
|
9494
|
+
return import(
|
|
9495
|
+
/* webpackIgnore: true */
|
|
9496
|
+
"fs/promises"
|
|
9497
|
+
);
|
|
7728
9498
|
}
|
|
7729
9499
|
async function _readNodeFile(fullPath) {
|
|
7730
9500
|
if (typeof window !== "undefined") {
|
|
@@ -7759,11 +9529,17 @@ async function _initWasm() {
|
|
|
7759
9529
|
_scanFn = mod.scan;
|
|
7760
9530
|
} else {
|
|
7761
9531
|
const { readFile } = await _fs2();
|
|
7762
|
-
const { join: join4 } = await import(
|
|
7763
|
-
|
|
9532
|
+
const { join: join4 } = await import(
|
|
9533
|
+
/* webpackIgnore: true */
|
|
9534
|
+
"path"
|
|
9535
|
+
);
|
|
9536
|
+
const { pathToFileURL } = await import(
|
|
9537
|
+
/* webpackIgnore: true */
|
|
9538
|
+
"url"
|
|
9539
|
+
);
|
|
7764
9540
|
const wasmDir = join4(__dirname, "assets", "wasm");
|
|
7765
9541
|
const wasmBinary = await readFile(join4(wasmDir, "dir_scanner_wasm_bg.wasm"));
|
|
7766
|
-
const glueUrl = pathToFileURL(join4(wasmDir, "dir_scanner_wasm.
|
|
9542
|
+
const glueUrl = pathToFileURL(join4(wasmDir, "dir_scanner_wasm.js")).href;
|
|
7767
9543
|
const mod = await import(
|
|
7768
9544
|
/* @vite-ignore */
|
|
7769
9545
|
glueUrl
|
|
@@ -7772,12 +9548,18 @@ async function _initWasm() {
|
|
|
7772
9548
|
_scanFn = mod.scan;
|
|
7773
9549
|
}
|
|
7774
9550
|
}
|
|
7775
|
-
var DEFAULT_GRAPH_TYPE = "
|
|
9551
|
+
var DEFAULT_GRAPH_TYPE = "qlever";
|
|
7776
9552
|
var FSS_BATCH_CHUNK_SIZE = 1e3;
|
|
7777
9553
|
var PENDING_LS_PREFIX = "cue:pending:";
|
|
7778
9554
|
async function _pendingFilePath(spaceId) {
|
|
7779
|
-
const { tmpdir } = await import(
|
|
7780
|
-
|
|
9555
|
+
const { tmpdir } = await import(
|
|
9556
|
+
/* webpackIgnore: true */
|
|
9557
|
+
"os"
|
|
9558
|
+
);
|
|
9559
|
+
const { join: join4 } = await import(
|
|
9560
|
+
/* webpackIgnore: true */
|
|
9561
|
+
"path"
|
|
9562
|
+
);
|
|
7781
9563
|
return join4(tmpdir(), `cue-sync-pending-${spaceId}.json`);
|
|
7782
9564
|
}
|
|
7783
9565
|
async function _loadPending(spaceId) {
|
|
@@ -8412,6 +10194,27 @@ var Cue = class _Cue {
|
|
|
8412
10194
|
_app;
|
|
8413
10195
|
_endpoints;
|
|
8414
10196
|
_isEmulator;
|
|
10197
|
+
_gis = null;
|
|
10198
|
+
/**
|
|
10199
|
+
* Reactive GIS service. Lazily constructed on first access.
|
|
10200
|
+
*
|
|
10201
|
+
* @example
|
|
10202
|
+
* ```ts
|
|
10203
|
+
* cue.gis.setProjectId('my-project');
|
|
10204
|
+
* cue.gis.onAvailableCategories(cats => ...);
|
|
10205
|
+
* cue.gis.setBbox([west, south, east, north]);
|
|
10206
|
+
* cue.gis.setSelectedCategories(new Set(['cadastre', 'building']));
|
|
10207
|
+
* ```
|
|
10208
|
+
*/
|
|
10209
|
+
get gis() {
|
|
10210
|
+
if (!this._gis) {
|
|
10211
|
+
this._gis = new CueGis(
|
|
10212
|
+
() => this.api.getAuthHeaders(),
|
|
10213
|
+
this._endpoints.gatewayUrl
|
|
10214
|
+
);
|
|
10215
|
+
}
|
|
10216
|
+
return this._gis;
|
|
10217
|
+
}
|
|
8415
10218
|
constructor(config = {}) {
|
|
8416
10219
|
const usingDefaults = !config.apiKey && !config.appId && !config.measurementId;
|
|
8417
10220
|
if (usingDefaults) {
|
|
@@ -9260,6 +11063,27 @@ async function syncHandler(options) {
|
|
|
9260
11063
|
if (!space) {
|
|
9261
11064
|
process.exit(1);
|
|
9262
11065
|
}
|
|
11066
|
+
if (verbose)
|
|
11067
|
+
console.info("Authenticating \u23F3");
|
|
11068
|
+
let user;
|
|
11069
|
+
try {
|
|
11070
|
+
user = await cue.auth.signInWithApiKey(key, space);
|
|
11071
|
+
} catch (err) {
|
|
11072
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
11073
|
+
console.error(`Couldn't authenticate. ${cause}`);
|
|
11074
|
+
process.exit(1);
|
|
11075
|
+
}
|
|
11076
|
+
if (verbose)
|
|
11077
|
+
console.info("Authenticated \u2705\n");
|
|
11078
|
+
const projectDoc = await cue.projects.getProject(space);
|
|
11079
|
+
if (projectDoc) {
|
|
11080
|
+
const isSuperAdmin = await cue.auth.checkSuperAdmin();
|
|
11081
|
+
const canSync = isSuperAdmin || projectDoc.syncers?.includes(user.uid) || projectDoc.admins?.includes(user.uid);
|
|
11082
|
+
if (!canSync) {
|
|
11083
|
+
console.error(`You don't have sync rights on project ${space}.`);
|
|
11084
|
+
process.exit(1);
|
|
11085
|
+
}
|
|
11086
|
+
}
|
|
9263
11087
|
if (verbose)
|
|
9264
11088
|
console.info("Building sync base \u23F3");
|
|
9265
11089
|
const resolvedPath = (0, import_path3.resolve)(path);
|
|
@@ -9297,18 +11121,6 @@ async function syncHandler(options) {
|
|
|
9297
11121
|
}
|
|
9298
11122
|
if (verbose)
|
|
9299
11123
|
console.info("Built sync base \u2705\n");
|
|
9300
|
-
if (verbose)
|
|
9301
|
-
console.info("Authenticating \u23F3");
|
|
9302
|
-
let user;
|
|
9303
|
-
try {
|
|
9304
|
-
user = await cue.auth.signInWithApiKey(key, space);
|
|
9305
|
-
} catch (err) {
|
|
9306
|
-
const cause = err instanceof Error ? err.message : String(err);
|
|
9307
|
-
console.error(`Couldn't authenticate. ${cause}`);
|
|
9308
|
-
process.exit(1);
|
|
9309
|
-
}
|
|
9310
|
-
if (verbose)
|
|
9311
|
-
console.info("Authenticated \u2705\n");
|
|
9312
11124
|
if (metadataOnly) {
|
|
9313
11125
|
console.info(`Pushing metadata for ${localFiles.length} file(s) \u23F3`);
|
|
9314
11126
|
await cue.api.sync.pushAllMetadata(localFiles, {
|