@masterportal/masterportalapi 2.1.1
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/.editorconfig +15 -0
- package/.eslintignore +6 -0
- package/.eslintrc +184 -0
- package/.husky/pre-push +4 -0
- package/CHANGELOG.md +217 -0
- package/License.txt +21 -0
- package/README.md +24 -0
- package/babel.config.json +6 -0
- package/example/README.md +7 -0
- package/example/config/localGeoJSON.js +956 -0
- package/example/config/portal.json +35 -0
- package/example/config/services.json +79 -0
- package/example/index.html +27 -0
- package/example/index.js +210 -0
- package/example/style.scss +59 -0
- package/jest.config.js +18 -0
- package/jest.setup.js +5 -0
- package/jsdoc.json +15 -0
- package/package.json +62 -0
- package/public/marker.svg +1 -0
- package/public/stringMarker.js +5 -0
- package/src/crs.js +122 -0
- package/src/defaults.js +38 -0
- package/src/index.js +30 -0
- package/src/layer/geojson/index.js +109 -0
- package/src/layer/geojson/style.js +61 -0
- package/src/layer/lib.js +32 -0
- package/src/layer/oaf.js +207 -0
- package/src/layer/terrain.js +72 -0
- package/src/layer/tileset.js +71 -0
- package/src/layer/vector.js +48 -0
- package/src/layer/vectorBase.js +39 -0
- package/src/layer/wfs.js +218 -0
- package/src/layer/wms.js +185 -0
- package/src/lib/coordsToPairs.js +15 -0
- package/src/lib/getInitialLayers.js +22 -0
- package/src/lib/load3DScript.js +22 -0
- package/src/lib/oafUtil.js +22 -0
- package/src/lib/setBackgroundImage.js +23 -0
- package/src/lib/wfsUtil.js +75 -0
- package/src/lib/zoomTo.js +40 -0
- package/src/maps/api.js +5 -0
- package/src/maps/map.js +35 -0
- package/src/maps/mapView.js +54 -0
- package/src/maps/ol/olMap.js +124 -0
- package/src/maps/olcs/3dUtils/fixedOverlaySynchronizer.js +39 -0
- package/src/maps/olcs/3dUtils/wmsRasterSynchronizer.js +348 -0
- package/src/maps/olcs/olcsMap.js +212 -0
- package/src/rawLayerList.js +88 -0
- package/src/searchAddress/gazetteerUrl.js +22 -0
- package/src/searchAddress/index.js +13 -0
- package/src/searchAddress/parse.js +196 -0
- package/src/searchAddress/search.js +184 -0
- package/src/searchAddress/searchGazetteer.js +52 -0
- package/src/searchAddress/showGeographicIdentifier.js +20 -0
- package/src/searchAddress/types.js +17 -0
- package/test/.eslintrc +7 -0
- package/test/crs.test.js +109 -0
- package/test/layer/geojson/index.test.js +133 -0
- package/test/layer/geojson/style.test.js +46 -0
- package/test/layer/lib.test.js +83 -0
- package/test/layer/oaf.test.js +245 -0
- package/test/layer/resources/oafFeatures.js +105 -0
- package/test/layer/resources/wfsFeatures.js +26 -0
- package/test/layer/resources/wfsFilter.js +17 -0
- package/test/layer/terrain.test.js +106 -0
- package/test/layer/tileset.test.js +116 -0
- package/test/layer/vector.test.js +76 -0
- package/test/layer/vectorBase.test.js +48 -0
- package/test/layer/wfs.test.js +388 -0
- package/test/layer/wms.test.js +256 -0
- package/test/lib/coordsToPairs.test.js +9 -0
- package/test/lib/getInitialLayers.test.js +28 -0
- package/test/lib/oafUtil.test.js +24 -0
- package/test/lib/setBackgroundImage.test.js +49 -0
- package/test/lib/wfsUtil.test.js +28 -0
- package/test/lib/zoomTo.test.js +74 -0
- package/test/map.test.js +258 -0
- package/test/mapView.test.js +59 -0
- package/test/rawLayerList.test.js +48 -0
- package/test/searchAddress/gazetteerSearchResults/address.js +175 -0
- package/test/searchAddress/gazetteerSearchResults/district.js +71 -0
- package/test/searchAddress/gazetteerSearchResults/houseNumber.js +32 -0
- package/test/searchAddress/gazetteerSearchResults/index.js +19 -0
- package/test/searchAddress/gazetteerSearchResults/parcel.js +44 -0
- package/test/searchAddress/gazetteerSearchResults/street.js +46 -0
- package/test/searchAddress/gazetteerSearchResults/streetKey.js +51 -0
- package/test/searchAddress/gazetteerUrl.test.js +22 -0
- package/test/searchAddress/parse.test.js +110 -0
- package/test/searchAddress/search.test.js +150 -0
- package/test/searchAddress/searchGazetteer.test.js +58 -0
- package/test/searchAddress/showGeographicIdentifier.test.js +22 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import OLCesium from "olcs/OLCesium.js";
|
|
2
|
+
import VectorSynchronizer from "olcs/VectorSynchronizer.js";
|
|
3
|
+
import FixedOverlaySynchronizer from "./3dUtils/fixedOverlaySynchronizer.js";
|
|
4
|
+
import WMSRasterSynchronizer from "./3dUtils/wmsRasterSynchronizer.js";
|
|
5
|
+
import {transform, get} from "ol/proj.js";
|
|
6
|
+
|
|
7
|
+
let mapIdCounter = 0;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Sets the cesium scene defaults from the config.js
|
|
11
|
+
* @param {Cesium.scene} scene Cesium scene.
|
|
12
|
+
* @param {Object} config optional configuration parameters.
|
|
13
|
+
* @returns {void}
|
|
14
|
+
*/
|
|
15
|
+
function setCesiumSceneDefaults (scene, config) {
|
|
16
|
+
let params;
|
|
17
|
+
|
|
18
|
+
if (Object.prototype.hasOwnProperty.call(config, "cesiumParameter")) {
|
|
19
|
+
params = config.cesiumParameter;
|
|
20
|
+
if (params?.fog) {
|
|
21
|
+
scene.fog.enabled = params.fog?.enabled ? params.fog.enabled : scene.fog.enabled;
|
|
22
|
+
scene.fog.density = params.fog?.density ? parseFloat(params.fog.density) : scene.fog.density;
|
|
23
|
+
scene.fog.screenSpaceErrorFactor = params.fog?.screenSpaceErrorFactor ? parseFloat(params.fog.screenSpaceErrorFactor) : scene.fog.screenSpaceErrorFactor;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
scene.globe.tileCacheSize = params?.tileCacheSize ? parseInt(params.tileCacheSize, 10) : scene.globe.tileCacheSize;
|
|
27
|
+
scene.globe.maximumScreenSpaceError = params?.maximumScreenSpaceError ? params.maximumScreenSpaceError : scene.globe.maximumScreenSpaceError;
|
|
28
|
+
scene.shadowMap.maximumDistance = 5000.0;
|
|
29
|
+
scene.shadowMap.darkness = 0.6;
|
|
30
|
+
scene.shadowMap.size = 2048;
|
|
31
|
+
scene.fxaa = params?.fxaa ? params.fxaa : scene.fxaa;
|
|
32
|
+
scene.globe.enableLighting = params?.enableLighting ? params.enableLighting : scene.globe.enableLighting;
|
|
33
|
+
scene.globe.depthTestAgainstTerrain = true;
|
|
34
|
+
scene.highDynamicRange = false;
|
|
35
|
+
scene.pickTranslucentDepth = true;
|
|
36
|
+
scene.camera.enableTerrainAdjustmentWhenLoading = true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Sets the camera parameters either from config or from an trigger.
|
|
42
|
+
* @param {Object} params Params.
|
|
43
|
+
* @param {Object} map3D olcs map.
|
|
44
|
+
* @param {Cesium} cesium cesium.
|
|
45
|
+
* @returns {void}
|
|
46
|
+
*/
|
|
47
|
+
export function setCameraParameter (params, map3D, cesium) {
|
|
48
|
+
let camera,
|
|
49
|
+
destination,
|
|
50
|
+
orientation;
|
|
51
|
+
|
|
52
|
+
// if the cameraPosition is given, directly set the cesium camera position, otherwise use olcesium Camera
|
|
53
|
+
if (map3D && params.cameraPosition) {
|
|
54
|
+
camera = map3D.getCesiumScene().camera;
|
|
55
|
+
destination = cesium.Cartesian3.fromDegrees(params.cameraPosition[0], params.cameraPosition[1], params.cameraPosition[2]);
|
|
56
|
+
orientation = {
|
|
57
|
+
heading: cesium.Math.toRadians(parseFloat(params.heading)),
|
|
58
|
+
pitch: cesium.Math.toRadians(parseFloat(params.pitch)),
|
|
59
|
+
roll: cesium.Math.toRadians(parseFloat(params.roll))
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
camera.setView({
|
|
63
|
+
destination,
|
|
64
|
+
orientation
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
else if (map3D !== undefined && params !== null) {
|
|
68
|
+
camera = map3D.getCamera();
|
|
69
|
+
if (params?.tilt) {
|
|
70
|
+
camera.setTilt(parseFloat(params.tilt));
|
|
71
|
+
}
|
|
72
|
+
if (params?.heading) {
|
|
73
|
+
camera.setHeading(parseFloat(params.heading));
|
|
74
|
+
}
|
|
75
|
+
if (params?.altitude) {
|
|
76
|
+
camera.setAltitude(parseFloat(params.altitude));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Reacts to 3D click event in cesium scene.
|
|
83
|
+
* @param {Event} event The cesium event.
|
|
84
|
+
* @returns {Function} Returns the callbackfunction with click informations.
|
|
85
|
+
*/
|
|
86
|
+
function reactTo3DClickEvent (event) {
|
|
87
|
+
const scene = this.scene,
|
|
88
|
+
ray = scene.camera.getPickRay(event.position),
|
|
89
|
+
cartesian = scene.globe.pick(ray, scene),
|
|
90
|
+
mapProjection = this.map3D.getOlView().getProjection();
|
|
91
|
+
let height,
|
|
92
|
+
coords,
|
|
93
|
+
cartographic,
|
|
94
|
+
distance,
|
|
95
|
+
resolution,
|
|
96
|
+
transformedCoords,
|
|
97
|
+
transformedPickedPosition,
|
|
98
|
+
pickedPositionCartesian,
|
|
99
|
+
cartographicPickedPosition,
|
|
100
|
+
clickObject;
|
|
101
|
+
|
|
102
|
+
if (cartesian) {
|
|
103
|
+
if (document.querySelector(".nav li")?.classList.contains("open")) {
|
|
104
|
+
document.querySelector(".nav li").classList.remove("open");
|
|
105
|
+
}
|
|
106
|
+
cartographic = scene.globe.ellipsoid.cartesianToCartographic(cartesian);
|
|
107
|
+
coords = [window.Cesium.Math.toDegrees(cartographic.longitude), window.Cesium.Math.toDegrees(cartographic.latitude)];
|
|
108
|
+
height = scene.globe.getHeight(cartographic);
|
|
109
|
+
if (height) {
|
|
110
|
+
coords = coords.concat([height]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
distance = window.Cesium.Cartesian3.distance(cartesian, scene.camera.position);
|
|
114
|
+
resolution = this.map3D.getCamera().calcResolutionForDistance(distance, cartographic.latitude);
|
|
115
|
+
transformedCoords = transform(coords, get("EPSG:4326"), mapProjection);
|
|
116
|
+
transformedPickedPosition = null;
|
|
117
|
+
|
|
118
|
+
if (scene.pickPositionSupported) {
|
|
119
|
+
pickedPositionCartesian = scene.pickPosition(event.position);
|
|
120
|
+
if (pickedPositionCartesian) {
|
|
121
|
+
cartographicPickedPosition = scene.globe.ellipsoid.cartesianToCartographic(pickedPositionCartesian);
|
|
122
|
+
transformedPickedPosition = transform([window.Cesium.Math.toDegrees(cartographicPickedPosition.longitude), window.Cesium.Math.toDegrees(cartographicPickedPosition.latitude)], get("EPSG:4326"), mapProjection);
|
|
123
|
+
transformedPickedPosition.push(cartographicPickedPosition.height);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
clickObject = {
|
|
127
|
+
map3D: this,
|
|
128
|
+
position: event.position,
|
|
129
|
+
pickedPosition: transformedPickedPosition,
|
|
130
|
+
coordinate: transformedCoords,
|
|
131
|
+
latitude: coords[0],
|
|
132
|
+
longitude: coords[1],
|
|
133
|
+
resolution: resolution,
|
|
134
|
+
originalEvent: event
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return this.callback(clickObject);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Logic to listen to click events in 3D mode.
|
|
142
|
+
* @param {Object} map3DObject Contains the scene, 3D map and a callback function.
|
|
143
|
+
* @returns {void}
|
|
144
|
+
*/
|
|
145
|
+
function handle3DEvents (map3DObject) {
|
|
146
|
+
let eventHandler;
|
|
147
|
+
|
|
148
|
+
if (window.Cesium) {
|
|
149
|
+
eventHandler = new window.Cesium.ScreenSpaceEventHandler(map3DObject.scene.canvas);
|
|
150
|
+
eventHandler.setInputAction(reactTo3DClickEvent.bind(map3DObject), window.Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Creates a 3D-map.
|
|
156
|
+
* @param {object} [settings] The settings for the 3D-map.
|
|
157
|
+
* @param {module:ol/PluggableMap~PluggableMap} [settings.map2D] The 2D-Map
|
|
158
|
+
* @param {Cesium.JulianDate} [settings.shadowTime] The shadow time in julian date format if undefined olcs default is Cesium.JulianDate.now().
|
|
159
|
+
* @returns {module:OLCesium} the 3d-map
|
|
160
|
+
*/
|
|
161
|
+
export function createMap (settings) {
|
|
162
|
+
const map3D = new OLCesium({
|
|
163
|
+
map: settings.map2D,
|
|
164
|
+
time: settings.shadowTime ? settings.shadowTime : undefined,
|
|
165
|
+
sceneOptions: {
|
|
166
|
+
shadows: false
|
|
167
|
+
},
|
|
168
|
+
stopOpenLayersEventsPropagation: true,
|
|
169
|
+
createSynchronizers: (olMap, scene) => {
|
|
170
|
+
return [new WMSRasterSynchronizer(olMap, scene), new VectorSynchronizer(olMap, scene), new FixedOverlaySynchronizer(olMap, scene)];
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
map3D.id = `map3D_${mapIdCounter++}`;
|
|
175
|
+
map3D.mapMode = "3D";
|
|
176
|
+
|
|
177
|
+
return map3D;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Prepares the camera and listens to camera changed events.
|
|
182
|
+
* @param {Cesium.scene} scene Cesium scene.
|
|
183
|
+
* @param {Object} urlParams optional urlParams for camera.
|
|
184
|
+
* @param {Object} map3D olcs map.
|
|
185
|
+
* @param {Object} config optional configuration parameters.
|
|
186
|
+
* @param {Cesium} cesium optional cesium.
|
|
187
|
+
* @returns {Cesium.scene.camera} Cesium camera.
|
|
188
|
+
*/
|
|
189
|
+
export function prepareCamera (scene, urlParams, map3D, config, cesium) {
|
|
190
|
+
const camera = scene.camera;
|
|
191
|
+
let cameraParameter = Object.prototype.hasOwnProperty.call(config, "cameraParameter") ? config.cameraParameter : {};
|
|
192
|
+
|
|
193
|
+
cameraParameter = urlParams?.altitude ? Object.assign(cameraParameter || {}, {altitude: urlParams?.altitude}) : cameraParameter;
|
|
194
|
+
cameraParameter = urlParams?.heading ? Object.assign(cameraParameter || {}, {heading: urlParams?.heading}) : cameraParameter;
|
|
195
|
+
cameraParameter = urlParams?.tilt ? Object.assign(cameraParameter || {}, {tilt: urlParams?.tilt}) : cameraParameter;
|
|
196
|
+
|
|
197
|
+
if (Object.keys(cameraParameter).length > 0) {
|
|
198
|
+
setCameraParameter(cameraParameter, map3D, cesium);
|
|
199
|
+
}
|
|
200
|
+
return camera;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Prepares the cesium scene.
|
|
205
|
+
* @param {Object} map3DObject Contains the scene, 3D map and a callback function.
|
|
206
|
+
* @param {Object} config optional configuration parameters.
|
|
207
|
+
* @returns {void}
|
|
208
|
+
*/
|
|
209
|
+
export function prepareScene (map3DObject, config) {
|
|
210
|
+
handle3DEvents(map3DObject);
|
|
211
|
+
setCesiumSceneDefaults(map3DObject.scene, config);
|
|
212
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import defaults from "./defaults";
|
|
2
|
+
|
|
3
|
+
/** layerList that contains all known services.
|
|
4
|
+
* @type{Array}
|
|
5
|
+
* @ignore
|
|
6
|
+
*/
|
|
7
|
+
let layerList = [];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Initializes the layer list with either an object or an URL. May be used again to override the layer list.
|
|
11
|
+
* createMap will call this for you, but won't notify you of when it's done. Use this function manually with a
|
|
12
|
+
* callback to know when layers can be added programmatically.
|
|
13
|
+
* @param {(string|object)} [layerConf="https://geoportal-hamburg.de/lgv-config/services-internet.json"] - either the URL to fetch the services from, or the object containing the services
|
|
14
|
+
* @param {function} [callback] - called with services after loaded; called with false and error on error
|
|
15
|
+
* @returns {undefined} nothing, add callback to receive layerList
|
|
16
|
+
*/
|
|
17
|
+
export function initializeLayerList (layerConf = defaults.layerConf, callback) {
|
|
18
|
+
if (Array.isArray(layerConf)) {
|
|
19
|
+
// case: parameter was services.json contents
|
|
20
|
+
layerList = layerConf;
|
|
21
|
+
if (typeof callback === "function") {
|
|
22
|
+
callback(layerList);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// case: parameter is URL
|
|
29
|
+
const Http = new XMLHttpRequest();
|
|
30
|
+
|
|
31
|
+
Http.open("GET", layerConf);
|
|
32
|
+
Http.timeout = 10000;
|
|
33
|
+
Http.send();
|
|
34
|
+
Http.onload = function () {
|
|
35
|
+
try {
|
|
36
|
+
layerList = JSON.parse(Http.responseText);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error("An error occured when parsing the response after loading '" + layerConf + "':", error);
|
|
40
|
+
return callback(false, error);
|
|
41
|
+
}
|
|
42
|
+
if (typeof callback === "function") {
|
|
43
|
+
return callback(layerList);
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
};
|
|
47
|
+
Http.onerror = function (e) {
|
|
48
|
+
console.error("An error occured when trying to fetch services from '" + layerConf + "':", e);
|
|
49
|
+
callback(false, e);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Returns the first entry in layerList matching the given searchAttributes.
|
|
55
|
+
* @param {object} searchAttributes - key/value-pairs to be searched for, e.g. { typ: "WMS" } to get the first WMS
|
|
56
|
+
* @returns {?object} first layer matching the searchAttributes or null if none was found
|
|
57
|
+
*/
|
|
58
|
+
export function getLayerWhere (searchAttributes) {
|
|
59
|
+
const keys = Object.keys(searchAttributes);
|
|
60
|
+
|
|
61
|
+
return layerList.find(entry => keys.every(key => entry[key] === searchAttributes[key])) || null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** @returns {object[]} complete layerList as initialized */
|
|
65
|
+
export function getLayerList () {
|
|
66
|
+
return layerList;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Returns display names map for a layer, or display name for a specific attribute.
|
|
71
|
+
* @param {string} layerId - if of layer to fetch display names for
|
|
72
|
+
* @param {string} [featureAttribute] - if given, only one entry of map is returned
|
|
73
|
+
* @returns {?(object|string)} - map of originalName->displayName or name of featureAttribute if specified; if layer or featureAttribute not found, null
|
|
74
|
+
*/
|
|
75
|
+
export function getDisplayNamesOfFeatureAttributes (layerId, featureAttribute) {
|
|
76
|
+
const attributes = getLayerWhere({id: layerId});
|
|
77
|
+
|
|
78
|
+
if (attributes && typeof featureAttribute === "string") {
|
|
79
|
+
const displayName = attributes.gfiAttributes && attributes.gfiAttributes[featureAttribute];
|
|
80
|
+
|
|
81
|
+
return typeof displayName === "string" ? displayName : null;
|
|
82
|
+
}
|
|
83
|
+
else if (attributes) {
|
|
84
|
+
return attributes.gfiAttributes || null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import defaults from "../defaults";
|
|
2
|
+
|
|
3
|
+
// Holds URL of gazetteer to use in search function
|
|
4
|
+
let gazetteerUrl = defaults.gazetteerUrl;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Sets the gazetteerUrl to be used by the search function.
|
|
8
|
+
* If parameter is falsy, url from defaults.js is kept.
|
|
9
|
+
* @param {String} url url to use for searchAddress
|
|
10
|
+
* @returns {void}
|
|
11
|
+
*/
|
|
12
|
+
export function setGazetteerUrl (url) {
|
|
13
|
+
url ? gazetteerUrl = url : null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Retrieves active gazetteer URL.
|
|
18
|
+
* @returns {string} currently set gazetteer URL
|
|
19
|
+
*/
|
|
20
|
+
export function getGazetteerUrl () {
|
|
21
|
+
return gazetteerUrl;
|
|
22
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {search} from "./search";
|
|
2
|
+
import {searchTypes} from "./types";
|
|
3
|
+
import {setGazetteerUrl, getGazetteerUrl} from "./gazetteerUrl";
|
|
4
|
+
import {setShowGeographicIdentifier, getShowGeographicIdentifier} from "./showGeographicIdentifier";
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
search,
|
|
8
|
+
searchTypes,
|
|
9
|
+
setGazetteerUrl,
|
|
10
|
+
getGazetteerUrl,
|
|
11
|
+
setShowGeographicIdentifier,
|
|
12
|
+
getShowGeographicIdentifier
|
|
13
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import xml2js from "xml2js";
|
|
2
|
+
|
|
3
|
+
import {searchTypes} from "./types";
|
|
4
|
+
import {getShowGeographicIdentifier} from "./showGeographicIdentifier";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Flattens xml2js result a little to avoid unnecessary one-element arrays.
|
|
8
|
+
* NOTE currently keeping special properties ($, _) of xml2js to keep attributes.
|
|
9
|
+
* @param {*} a anything
|
|
10
|
+
* @returns {*} a's first element if one-element array, else a
|
|
11
|
+
* @ignore
|
|
12
|
+
*/
|
|
13
|
+
function flatten (a) {
|
|
14
|
+
if (Array.isArray(a) && a.length === 1) {
|
|
15
|
+
return flatten(a[0]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Recursively searches an object for a key.
|
|
22
|
+
* Since parsed XML tends to contain extra arrays, those are stepped into.
|
|
23
|
+
* @param {(object|Array)} o object to deep-search for key
|
|
24
|
+
* @param {string} key key to search for
|
|
25
|
+
* @returns {*} value behind key
|
|
26
|
+
* @ignore
|
|
27
|
+
*/
|
|
28
|
+
function findKey (o, key) {
|
|
29
|
+
if (Array.isArray(o)) {
|
|
30
|
+
return o.map(x => findKey(x, key)).filter(x => x)[0];
|
|
31
|
+
}
|
|
32
|
+
if (typeof o !== "object") {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (typeof o[key] !== "undefined") {
|
|
36
|
+
return o[key];
|
|
37
|
+
}
|
|
38
|
+
return Object.keys(o).map(oKey => findKey(o[oKey], key)).filter(x => x)[0];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Simply copies over all properties it can find.
|
|
43
|
+
* @param {object} entry xml2js-parsed result entry
|
|
44
|
+
* @returns {object} copied and flattened properties
|
|
45
|
+
* @ignore
|
|
46
|
+
*/
|
|
47
|
+
function getAllPropertiesFlat (entry) {
|
|
48
|
+
const properties = {
|
|
49
|
+
objectType: Object.keys(entry)[0]
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
Object.keys(entry[properties.objectType][0]).forEach(propertyName => {
|
|
53
|
+
properties[propertyName] = flatten(entry[properties.objectType][0][propertyName]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return properties;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parses coordinates from wkt string.
|
|
61
|
+
* @param {string} wkt position as wkt
|
|
62
|
+
* @returns {string} position as coordinates
|
|
63
|
+
*/
|
|
64
|
+
function parseCoordinatesFromWkt (wkt) {
|
|
65
|
+
let pos = [];
|
|
66
|
+
|
|
67
|
+
if (wkt.startsWith("POINT")) {
|
|
68
|
+
pos = wkt.split(")")[0]?.split("(")[1];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return pos;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Parses result entries; uses type-specific rules to determine name/geometry.
|
|
76
|
+
* @param {object} entry xml2js-parsed result entry
|
|
77
|
+
* @param {string} type type string
|
|
78
|
+
* @returns {SearchResult} parsed search result for districts
|
|
79
|
+
* @ignore
|
|
80
|
+
*/
|
|
81
|
+
function parseEntry (entry, type) {
|
|
82
|
+
const searchResult = {
|
|
83
|
+
type,
|
|
84
|
+
properties: getAllPropertiesFlat(entry)
|
|
85
|
+
};
|
|
86
|
+
let pos = searchResult.properties?.pos ? parseCoordinatesFromWkt(searchResult.properties.pos) : findKey(searchResult.properties, "pos")[0],
|
|
87
|
+
hausnummer = "",
|
|
88
|
+
hausnummernzusatz = "",
|
|
89
|
+
strassenname = "";
|
|
90
|
+
|
|
91
|
+
// setting name
|
|
92
|
+
switch (type) {
|
|
93
|
+
case searchTypes.DISTRICT:
|
|
94
|
+
searchResult.name = searchResult.properties.geographicIdentifier?._ ? searchResult.properties.geographicIdentifier._ : searchResult.properties.geographicIdentifier;
|
|
95
|
+
break;
|
|
96
|
+
case searchTypes.STREET_KEY:
|
|
97
|
+
case searchTypes.STREET:
|
|
98
|
+
if (searchResult.properties?.position_strassenachse) {
|
|
99
|
+
pos = findKey(searchResult.properties?.position_strassenachse, "pos")[0];
|
|
100
|
+
}
|
|
101
|
+
searchResult.name = getShowGeographicIdentifier() ? searchResult.properties.geographicIdentifier._ : searchResult.properties.strassenname;
|
|
102
|
+
break;
|
|
103
|
+
case searchTypes.PARCEL:
|
|
104
|
+
searchResult.name = `${searchResult.properties.gemarkung}/${searchResult.properties.flurstuecksnummer}`;
|
|
105
|
+
break;
|
|
106
|
+
case searchTypes.HOUSE_NUMBERS_FOR_STREET:
|
|
107
|
+
// house number like "11a" as name is not very telling - use "Streetname 11a" and save "11a" separately in properties
|
|
108
|
+
hausnummer = searchResult.properties.hausnummer?._ ? searchResult.properties.hausnummer._ : searchResult.properties.hausnummer;
|
|
109
|
+
|
|
110
|
+
if (searchResult.properties.hausnummernzusatz) {
|
|
111
|
+
hausnummernzusatz = searchResult.properties.hausnummernzusatz?._ ? searchResult.properties.hausnummernzusatz._ : searchResult.properties.hausnummernzusatz;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
searchResult.properties.hausnummerkomplett = `${hausnummer}${hausnummernzusatz}`;
|
|
115
|
+
searchResult.name =
|
|
116
|
+
getShowGeographicIdentifier() ? searchResult.properties.geographicIdentifier._ :
|
|
117
|
+
(searchResult.properties?.geographicIdentifier ? searchResult.properties.geographicIdentifier._.split(searchResult.properties.hausnummerkomplett)[0] : searchResult.properties.strassenname + " ") +
|
|
118
|
+
searchResult.properties.hausnummerkomplett;
|
|
119
|
+
break;
|
|
120
|
+
case searchTypes.ADDRESS_AFFIXED:
|
|
121
|
+
strassenname = searchResult.properties.strassenname?._ ? searchResult.properties.strassenname._ : searchResult.properties.strassenname;
|
|
122
|
+
hausnummer = searchResult.properties.hausnummer?._ ? searchResult.properties.hausnummer._ : searchResult.properties.hausnummer;
|
|
123
|
+
hausnummernzusatz = searchResult.properties.hausnummernzusatz?._ ? searchResult.properties.hausnummernzusatz._ : searchResult.properties.hausnummernzusatz;
|
|
124
|
+
|
|
125
|
+
searchResult.name = getShowGeographicIdentifier() ? searchResult.properties.geographicIdentifier._ : `${strassenname} ${hausnummer}${hausnummernzusatz}`;
|
|
126
|
+
break;
|
|
127
|
+
case searchTypes.ADDRESS_UNAFFIXED:
|
|
128
|
+
strassenname = searchResult.properties.strassenname?._ ? searchResult.properties.strassenname._ : searchResult.properties.strassenname;
|
|
129
|
+
hausnummer = searchResult.properties.hausnummer?._ ? searchResult.properties.hausnummer._ : searchResult.properties.hausnummer;
|
|
130
|
+
|
|
131
|
+
searchResult.name =
|
|
132
|
+
getShowGeographicIdentifier() ? searchResult.properties.geographicIdentifier._ :
|
|
133
|
+
(searchResult.properties?.geographicIdentifier ? searchResult.properties.geographicIdentifier._.split(hausnummer)[0] : strassenname + " ") + hausnummer;
|
|
134
|
+
break;
|
|
135
|
+
default:
|
|
136
|
+
searchResult.name = null;
|
|
137
|
+
console.error(`Unknown type in searchAddress.parse: '${type}'. Could not set name.`);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// setting geometry
|
|
142
|
+
switch (type) {
|
|
143
|
+
case searchTypes.DISTRICT:
|
|
144
|
+
case searchTypes.STREET_KEY:
|
|
145
|
+
case searchTypes.STREET:
|
|
146
|
+
case searchTypes.PARCEL:
|
|
147
|
+
case searchTypes.HOUSE_NUMBERS_FOR_STREET:
|
|
148
|
+
case searchTypes.ADDRESS_AFFIXED:
|
|
149
|
+
case searchTypes.ADDRESS_UNAFFIXED:
|
|
150
|
+
// for these, all polygon points are identical - using point is more clear
|
|
151
|
+
searchResult.geometry = {
|
|
152
|
+
type: "Point",
|
|
153
|
+
coordinates: pos.split(" ")
|
|
154
|
+
};
|
|
155
|
+
break;
|
|
156
|
+
default:
|
|
157
|
+
searchResult.geometry = null;
|
|
158
|
+
console.error(`Unknown type in searchAddress.parse: '${type}'. Could not set geometry.`);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return searchResult;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Parses gazetteer xml to search objects.
|
|
167
|
+
* @param {string} key internal name for query froms searchTypes
|
|
168
|
+
* @param {string} xmlString gazetteer answer as xml string
|
|
169
|
+
* @returns {Promise<SearchResult[]>} parsed response
|
|
170
|
+
* @ignore
|
|
171
|
+
*/
|
|
172
|
+
export function parse (key, xmlString) {
|
|
173
|
+
return new Promise((resolve, reject) => {
|
|
174
|
+
// tag processor "stripPrefix" removes e.g. "dog:" prefix on properties
|
|
175
|
+
xml2js.parseString(xmlString, {tagNameProcessors: [xml2js.processors.stripPrefix]},
|
|
176
|
+
(err, source) => {
|
|
177
|
+
if (err) {
|
|
178
|
+
reject(err);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const searchResults = source.FeatureCollection?.member
|
|
184
|
+
? source.FeatureCollection.member
|
|
185
|
+
.map(entry => parseEntry(entry, key))
|
|
186
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
187
|
+
: [];
|
|
188
|
+
|
|
189
|
+
resolve(searchResults);
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
reject(e);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|