diff --git a/.dockerignore b/.dockerignore
index fc409a8..8687cf3 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -7,3 +7,4 @@ node_modules
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
+examples/
diff --git a/.gitignore b/.gitignore
index 6635cf5..08bef08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ node_modules
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
+examples/assets/
diff --git a/.prettierignore b/.prettierignore
index d715da5..14109c7 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -12,3 +12,5 @@ static
pnpm-lock.yaml
package-lock.json
yarn.lock
+
+examples/assets/
diff --git a/README.md b/README.md
index 15364ca..97fa2a9 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
# MapColonies Playground
+
Code playground for mapping clients.
The code examples are saved in S3 bucket and fetched and cached by the server.
@@ -21,62 +22,63 @@ The code examples are saved in S3 bucket and fetched and cached by the server.
The codes examples shown are defined by a json index file with all the data needed. All the files listed in the index are loaded from the same bucket as the index itself.
### example index
+
```json
{
- "ol": {
- "basic-ol": {
- "displayName": "basic openlayers",
- "files": ["openlayers_basic.js", "openlayers.css", "openlayers.html"],
- "links": [
- { "name": "ol.css", "url": "/libs/ol/v7.3.0/ol.css", "type": "css" },
- { "name": "ol.js", "url": "/libs/ol/v7.3.0/ol.js", "type": "js" }
- ]
- },
- "geojson-style-ol": {
- "displayName": "geojson openlayers",
- "files": ["openlayers_geojson.js", "openlayers.css", "openlayers.html"],
- "links": [
- { "name": "ol.css", "url": "/libs/ol/v7.3.0/ol.css", "type": "css" },
- { "name": "ol.js", "url": "/libs/ol/v7.3.0/ol.js", "type": "js" }
- ]
- }
- },
- "cesium": {
- "basic-cesium": {
- "displayName": "basic cesium",
- "files": ["cesium_basic.js", "cesium.html"],
- "links": [
- {
- "name": "cesium.js",
- "url": "/libs/Cesium/Cesium.js",
- "type": "js"
- },
- {
- "name": "widgets.css",
- "url": "/libs/Cesium/Widgets/widgets.css",
- "type": "css"
- }
- ]
- }
- },
- "leaflet": {
- "basic-leaflet": {
- "displayName": "basic leaflet",
- "files": ["leaflet_basic.js", "leaflet.css", "leaflet.html"],
- "links": [
- {
- "name": "leaflet.js",
- "url": "/libs/leaflet/1.9.4/leaflet.js",
- "type": "js"
- },
- {
- "name": "leaflet.css",
- "url": "/libs/leaflet/1.9.4/leaflet.css",
- "type": "css"
- }
- ]
- }
- }
+ "ol": {
+ "basic-ol": {
+ "displayName": "basic openlayers",
+ "files": ["openlayers_basic.js", "openlayers.css", "openlayers.html"],
+ "links": [
+ { "name": "ol.css", "url": "/libs/ol/v7.3.0/ol.css", "type": "css" },
+ { "name": "ol.js", "url": "/libs/ol/v7.3.0/ol.js", "type": "js" }
+ ]
+ },
+ "geojson-style-ol": {
+ "displayName": "geojson openlayers",
+ "files": ["openlayers_geojson.js", "openlayers.css", "openlayers.html"],
+ "links": [
+ { "name": "ol.css", "url": "/libs/ol/v7.3.0/ol.css", "type": "css" },
+ { "name": "ol.js", "url": "/libs/ol/v7.3.0/ol.js", "type": "js" }
+ ]
+ }
+ },
+ "cesium": {
+ "basic-cesium": {
+ "displayName": "basic cesium",
+ "files": ["cesium_basic.js", "cesium.html"],
+ "links": [
+ {
+ "name": "cesium.js",
+ "url": "/libs/Cesium/Cesium.js",
+ "type": "js"
+ },
+ {
+ "name": "widgets.css",
+ "url": "/libs/Cesium/Widgets/widgets.css",
+ "type": "css"
+ }
+ ]
+ }
+ },
+ "leaflet": {
+ "basic-leaflet": {
+ "displayName": "basic leaflet",
+ "files": ["leaflet_basic.js", "leaflet.css", "leaflet.html"],
+ "links": [
+ {
+ "name": "leaflet.js",
+ "url": "/libs/leaflet/1.9.4/leaflet.js",
+ "type": "js"
+ },
+ {
+ "name": "leaflet.css",
+ "url": "/libs/leaflet/1.9.4/leaflet.css",
+ "type": "css"
+ }
+ ]
+ }
+ }
}
```
diff --git a/examples/cesium-3d/3d-model.js b/examples/cesium-3d/3d-model.js
new file mode 100644
index 0000000..9698ebd
--- /dev/null
+++ b/examples/cesium-3d/3d-model.js
@@ -0,0 +1,75 @@
+import { TOKEN } from './config/common-config.js';
+import {
+ PRODUCT_ID as RASTER_PRODUCT_ID,
+ PRODUCT_TYPE as RASTER_PRODUCT_TYPE,
+ LAYER_IMAGE_FORMAT
+} from './config/raster-config.js';
+import {
+ PRODUCT_ID as DEM_PRODUCT_ID,
+ PRODUCT_TYPE as DEM_PRODUCT_TYPE,
+ DEM_SCHEME
+} from './config/dem-config.js';
+import {
+ PRODUCT_ID as MODEL_3D_PRODUCT_ID,
+ PRODUCT_TYPE as MODEL_3D_PRODUCT_TYPE,
+ MODEL_3D_SCHEME
+} from './config/3d-config.js';
+import { fetchServiceLink } from './utils/catalog-client.js';
+import { fetchWmtsTileTemplate } from './utils/wmts-utils.js';
+
+Promise.all([
+ fetchWmtsTileTemplate(RASTER_PRODUCT_ID, RASTER_PRODUCT_TYPE, LAYER_IMAGE_FORMAT),
+ fetchServiceLink('dem', DEM_PRODUCT_ID, DEM_PRODUCT_TYPE, DEM_SCHEME),
+ fetchServiceLink('3d', MODEL_3D_PRODUCT_ID, MODEL_3D_PRODUCT_TYPE, MODEL_3D_SCHEME)
+]).then(([raster, dem, model]) => {
+ const viewer = new Cesium.Viewer('cesiumContainer', {
+ baseLayer: new Cesium.ImageryLayer(
+ new Cesium.WebMapTileServiceImageryProvider({
+ url: new Cesium.Resource({
+ url: raster.template,
+ queryParameters: {
+ token: TOKEN
+ }
+ }),
+ layer: raster.name,
+ style: 'default',
+ format: LAYER_IMAGE_FORMAT,
+ tileMatrixSetID: 'WorldCRS84',
+ tilingScheme: new Cesium.GeographicTilingScheme()
+ })
+ ),
+ terrainProvider: new Cesium.CesiumTerrainProvider({
+ url: new Cesium.Resource({
+ url: dem.url,
+ queryParameters: {
+ token: TOKEN
+ }
+ })
+ })
+ });
+
+ viewer.scene.primitives.add(
+ new Cesium.Cesium3DTileset({
+ url: new Cesium.Resource({
+ url: model.url,
+ queryParameters: {
+ token: TOKEN
+ }
+ }),
+ maximumScreenSpaceError: 5,
+ cullRequestsWhileMovingMultiplier: 120,
+ preloadFlightDestination: true,
+ preferLeaves: true,
+ skipLevelOfDetail: true
+ })
+ );
+
+ viewer.camera.flyTo({
+ destination: Cesium.Cartesian3.fromDegrees(35.201436, 33.265378, 300),
+ orientation: {
+ heading: Cesium.Math.toRadians(25.0),
+ pitch: Cesium.Math.toRadians(-10.0),
+ roll: 0.0
+ }
+ });
+});
diff --git a/examples/cesium-3d/cesium.html b/examples/cesium-3d/cesium.html
new file mode 100644
index 0000000..95872da
--- /dev/null
+++ b/examples/cesium-3d/cesium.html
@@ -0,0 +1 @@
+
diff --git a/examples/cesium-geocoding/cesium.html b/examples/cesium-geocoding/cesium.html
new file mode 100644
index 0000000..95872da
--- /dev/null
+++ b/examples/cesium-geocoding/cesium.html
@@ -0,0 +1 @@
+
diff --git a/examples/cesium-geocoding/cesium.js b/examples/cesium-geocoding/cesium.js
new file mode 100644
index 0000000..14417e2
--- /dev/null
+++ b/examples/cesium-geocoding/cesium.js
@@ -0,0 +1,64 @@
+'use strict';
+import { TOKEN } from './config/common-config.js';
+import { PRODUCT_ID, PRODUCT_TYPE, LAYER_IMAGE_FORMAT } from './config/raster-config.js';
+import { GEOCODING_URL } from './config/vector-config.js';
+import { fetchWmtsTileTemplate } from './utils/wmts-utils.js';
+
+function OpenStreetMapNominatimGeocoder() {}
+
+OpenStreetMapNominatimGeocoder.prototype.geocode = function (input) {
+ const resource = new Cesium.Resource({
+ url: GEOCODING_URL,
+ queryParameters: {
+ format: 'json',
+ q: input,
+ token: TOKEN
+ },
+ headers: {
+ 'accept-language': 'he'
+ }
+ });
+
+ return resource.fetchJson().then(function (results) {
+ let bboxDegrees;
+ return results.map(function (resultObject) {
+ bboxDegrees = resultObject.boundingbox;
+ return {
+ displayName: resultObject.display_name,
+ destination: Cesium.Rectangle.fromDegrees(
+ bboxDegrees[2],
+ bboxDegrees[0],
+ bboxDegrees[3],
+ bboxDegrees[1]
+ )
+ };
+ });
+ });
+};
+
+fetchWmtsTileTemplate(PRODUCT_ID, PRODUCT_TYPE, LAYER_IMAGE_FORMAT).then(({ template, name }) => {
+ new Cesium.Viewer('cesiumContainer', {
+ vrButton: false,
+ homeButton: false,
+ infoBox: false,
+ timeline: false,
+ navigationHelpButton: false,
+ shouldAnimate: false,
+ baseLayer: new Cesium.ImageryLayer(
+ new Cesium.WebMapTileServiceImageryProvider({
+ url: new Cesium.Resource({
+ url: template,
+ queryParameters: {
+ token: TOKEN
+ }
+ }),
+ layer: name,
+ style: 'default',
+ format: LAYER_IMAGE_FORMAT,
+ tileMatrixSetID: 'WorldCRS84',
+ tilingScheme: new Cesium.GeographicTilingScheme()
+ })
+ ),
+ geocoder: new OpenStreetMapNominatimGeocoder()
+ });
+});
diff --git a/examples/cesium-layers-split/split-layers.css b/examples/cesium-layers-split/split-layers.css
new file mode 100644
index 0000000..a5b0d59
--- /dev/null
+++ b/examples/cesium-layers-split/split-layers.css
@@ -0,0 +1,13 @@
+#slider {
+ position: absolute;
+ left: 50%;
+ top: 0px;
+ background-color: #d3d3d3;
+ width: 5px;
+ height: 100%;
+ z-index: 9999;
+}
+
+#slider:hover {
+ cursor: ew-resize;
+}
diff --git a/examples/cesium-layers-split/split-layers.html b/examples/cesium-layers-split/split-layers.html
new file mode 100644
index 0000000..61c6b90
--- /dev/null
+++ b/examples/cesium-layers-split/split-layers.html
@@ -0,0 +1,4 @@
+
+
diff --git a/examples/cesium-layers-split/split-layers.js b/examples/cesium-layers-split/split-layers.js
new file mode 100644
index 0000000..ca2b78e
--- /dev/null
+++ b/examples/cesium-layers-split/split-layers.js
@@ -0,0 +1,83 @@
+'use strict';
+import { TOKEN } from './config/common-config.js';
+import { PRODUCT_ID, PRODUCT_TYPE, LAYER_IMAGE_FORMAT } from './config/raster-config.js';
+import { fetchWmtsTileTemplate } from './utils/wmts-utils.js';
+
+Promise.all([
+ fetchWmtsTileTemplate(PRODUCT_ID, PRODUCT_TYPE, LAYER_IMAGE_FORMAT),
+ fetchWmtsTileTemplate('OSM', 'RasterVectorBest', LAYER_IMAGE_FORMAT)
+]).then(([main, second]) => {
+ const viewer = new Cesium.Viewer('cesiumContainer', {
+ baseLayer: new Cesium.ImageryLayer(
+ new Cesium.WebMapTileServiceImageryProvider({
+ url: new Cesium.Resource({
+ url: main.template,
+ queryParameters: {
+ token: TOKEN
+ }
+ }),
+ layer: main.name,
+ style: 'default',
+ format: LAYER_IMAGE_FORMAT,
+ tileMatrixSetID: 'WorldCRS84',
+ tilingScheme: new Cesium.GeographicTilingScheme()
+ })
+ ),
+ baseLayerPicker: false,
+ infoBox: false
+ });
+
+ const layers = viewer.imageryLayers;
+ const secondLayer = layers.addImageryProvider(
+ new Cesium.WebMapTileServiceImageryProvider({
+ url: new Cesium.Resource({
+ url: second.template,
+ queryParameters: {
+ token: TOKEN
+ }
+ }),
+ layer: second.name,
+ style: 'default',
+ format: LAYER_IMAGE_FORMAT,
+ tileMatrixSetID: 'WorldCRS84',
+ tilingScheme: new Cesium.GeographicTilingScheme()
+ })
+ );
+ secondLayer.splitDirection = Cesium.SplitDirection.LEFT; // Only show to the left of the slider.
+
+ // Sync the position of the slider with the split position
+ const slider = document.getElementById('slider');
+ viewer.scene.splitPosition = slider.offsetLeft / slider.parentElement.offsetWidth;
+
+ const handler = new Cesium.ScreenSpaceEventHandler(slider);
+
+ let moveActive = false;
+
+ function move(movement) {
+ if (!moveActive) {
+ return;
+ }
+
+ const relativeOffset = movement.endPosition.x;
+ const splitPosition = (slider.offsetLeft + relativeOffset) / slider.parentElement.offsetWidth;
+ slider.style.left = `${100.0 * splitPosition}%`;
+ viewer.scene.splitPosition = splitPosition;
+ }
+
+ handler.setInputAction(function () {
+ moveActive = true;
+ }, Cesium.ScreenSpaceEventType.LEFT_DOWN);
+ handler.setInputAction(function () {
+ moveActive = true;
+ }, Cesium.ScreenSpaceEventType.PINCH_START);
+
+ handler.setInputAction(move, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
+ handler.setInputAction(move, Cesium.ScreenSpaceEventType.PINCH_MOVE);
+
+ handler.setInputAction(function () {
+ moveActive = false;
+ }, Cesium.ScreenSpaceEventType.LEFT_UP);
+ handler.setInputAction(function () {
+ moveActive = false;
+ }, Cesium.ScreenSpaceEventType.PINCH_END);
+});
diff --git a/examples/cesium-terrain/cesium.html b/examples/cesium-terrain/cesium.html
new file mode 100644
index 0000000..95872da
--- /dev/null
+++ b/examples/cesium-terrain/cesium.html
@@ -0,0 +1 @@
+
diff --git a/examples/cesium-terrain/cesium.js b/examples/cesium-terrain/cesium.js
new file mode 100644
index 0000000..1424df5
--- /dev/null
+++ b/examples/cesium-terrain/cesium.js
@@ -0,0 +1,73 @@
+import { TOKEN } from './config/common-config.js';
+import {
+ PRODUCT_ID as RASTER_PRODUCT_ID,
+ PRODUCT_TYPE as RASTER_PRODUCT_TYPE,
+ LAYER_IMAGE_FORMAT
+} from './config/raster-config.js';
+import {
+ PRODUCT_ID as DEM_PRODUCT_ID,
+ PRODUCT_TYPE as DEM_PRODUCT_TYPE,
+ DEM_SCHEME
+} from './config/dem-config.js';
+import { fetchServiceLink } from './utils/catalog-client.js';
+import { fetchWmtsTileTemplate } from './utils/wmts-utils.js';
+
+Promise.all([
+ fetchWmtsTileTemplate(RASTER_PRODUCT_ID, RASTER_PRODUCT_TYPE, LAYER_IMAGE_FORMAT),
+ fetchServiceLink('dem', DEM_PRODUCT_ID, DEM_PRODUCT_TYPE, DEM_SCHEME)
+]).then(([raster, dem]) => {
+ const viewer = new Cesium.Viewer('cesiumContainer', {
+ baseLayer: new Cesium.ImageryLayer(
+ new Cesium.WebMapTileServiceImageryProvider({
+ url: new Cesium.Resource({
+ url: raster.template,
+ queryParameters: {
+ token: TOKEN
+ }
+ }),
+ layer: raster.name,
+ style: 'default',
+ format: LAYER_IMAGE_FORMAT,
+ tileMatrixSetID: 'newGrids',
+ tilingScheme: new Cesium.GeographicTilingScheme()
+ })
+ ),
+ terrainProvider: new Cesium.CesiumTerrainProvider({
+ url: new Cesium.Resource({
+ url: dem.url,
+ queryParameters: {
+ token: TOKEN
+ }
+ })
+ })
+ });
+
+ viewer.camera.flyTo({
+ destination: Cesium.Cartesian3.fromDegrees(35.567306, 33.210784, 6000),
+ orientation: {
+ heading: Cesium.Math.toRadians(25.0),
+ pitch: Cesium.Math.toRadians(-10.0),
+ roll: 0.0
+ }
+ });
+
+ fetchWmtsTileTemplate('WORLD_MAP_BASE_THIN', 'RasterVectorBest', 'image/png').then(
+ ({ template, name }) => {
+ viewer.imageryLayers.addImageryProvider(
+ new Cesium.WebMapTileServiceImageryProvider({
+ url: new Cesium.Resource({
+ url: template,
+ queryParameters: {
+ token: TOKEN
+ }
+ }),
+ layer: name,
+ style: 'default',
+ format: 'image/png',
+ tileMatrixSetID: 'newGrids',
+ tilingScheme: new Cesium.GeographicTilingScheme()
+ })
+ );
+ }
+ );
+});
diff --git a/examples/cesium-viewshed/viewshed.css b/examples/cesium-viewshed/viewshed.css
new file mode 100644
index 0000000..e69de29
diff --git a/examples/cesium-viewshed/viewshed.html b/examples/cesium-viewshed/viewshed.html
new file mode 100644
index 0000000..95872da
--- /dev/null
+++ b/examples/cesium-viewshed/viewshed.html
@@ -0,0 +1 @@
+
diff --git a/examples/cesium-viewshed/viewshed.js b/examples/cesium-viewshed/viewshed.js
new file mode 100644
index 0000000..874cfeb
--- /dev/null
+++ b/examples/cesium-viewshed/viewshed.js
@@ -0,0 +1,713 @@
+import { TOKEN } from './config/common-config.js';
+import {
+ PRODUCT_ID as RASTER_PRODUCT_ID,
+ PRODUCT_TYPE as RASTER_PRODUCT_TYPE,
+ LAYER_IMAGE_FORMAT
+} from './config/raster-config.js';
+import {
+ PRODUCT_ID as DEM_PRODUCT_ID,
+ PRODUCT_TYPE as DEM_PRODUCT_TYPE,
+ DEM_SCHEME
+} from './config/dem-config.js';
+import {
+ PRODUCT_ID as MODEL_3D_PRODUCT_ID,
+ PRODUCT_TYPE as MODEL_3D_PRODUCT_TYPE,
+ MODEL_3D_SCHEME
+} from './config/3d-config.js';
+import { fetchServiceLink } from './utils/catalog-client.js';
+import { fetchWmtsTileTemplate } from './utils/wmts-utils.js';
+
+const fsShaderText = `
+#define USE_NORMAL_SHADING
+uniform float view_distance; // Maximum distance for shadow effect
+uniform vec3 viewArea_color; // Color for visible areas
+uniform vec3 shadowArea_color; // Color for invisible areas
+uniform float percentShade; // Mix number for color blending
+uniform sampler2D colorTexture; // Texture for color
+uniform sampler2D shadowMap; // Shadow map texture
+uniform sampler2D depthTexture; // Depth texture
+uniform mat4 shadowMap_matrix; // Shadow map matrix
+uniform vec3 viewPosition_WC; // Uniform for view position
+uniform vec3 cameraPosition_WC; // Uniform for camera position
+uniform vec4 shadowMap_camera_positionEC; // Light position in eye coordinates
+uniform vec4 shadowMap_camera_directionEC; // Light direction in eye coordinates
+uniform vec3 ellipsoidInverseRadii;
+uniform vec3 shadowMap_camera_up; // Light up direction
+uniform vec3 shadowMap_camera_dir; // Light direction
+uniform vec3 shadowMap_camera_right; // Light right direction
+uniform vec4 shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness; // Shadow map parameters
+uniform vec4 shadowMap_texelSizeDepthBiasAndNormalShadingSmooth; // Shadow map parameters
+uniform vec4 _shadowMap_cascadeSplits[2];
+uniform mat4 _shadowMap_cascadeMatrices[4];
+uniform vec4 _shadowMap_cascadeDistances;
+uniform bool exclude_terrain;
+
+in vec2 v_textureCoordinates;
+out vec4 FragColor;
+
+vec4 toEye(in vec2 uv, in float depth){
+ float x = uv.x * 2.0 - 1.0;
+ float y = uv.y * 2.0 - 1.0;
+ vec4 camPosition = czm_inverseProjection * vec4(x, y, depth, 1.0);
+ float reciprocalW = 1.0 / camPosition.w;
+ camPosition *= reciprocalW;
+ return camPosition;
+}
+
+// This function gets the depth from a depth texture.
+float getDepth(in vec4 depth){
+ // Unpack the depth value from the depth texture
+ float z_window = czm_unpackDepth(depth);
+ // Reverse the logarithmic depth value to get the linear depth
+ z_window = czm_reverseLogDepth(z_window);
+ // Get the near and far values of the depth range
+ float n_range = czm_depthRange.near;
+ float f_range = czm_depthRange.far;
+ // Convert the depth value from window coordinates to normalized device coordinates
+ return (2.0 * z_window - n_range - f_range) / (f_range - n_range);
+}
+
+/**
+ * Projects a point onto a plane.
+ *
+ * @param planeNormal - A vector representing the normal of the plane.
+ * @param planeOrigin - A point on the plane.
+ * @param point - The point to be projected onto the plane.
+ * @return The projection of the point on the plane.
+ */
+vec3 pointProjectOnPlane(in vec3 planeNormal, in vec3 planeOrigin, in vec3 point){
+ // Calculate the vector from the plane origin to the point
+ vec3 v01 = point - planeOrigin;
+
+ // Calculate the perpendicular distance from the point to the plane
+ float d = dot(planeNormal, v01);
+
+ // Subtract the product of the plane normal and d from the point
+ // to get the projection of the point on the plane
+ return (point - planeNormal * d);
+}
+
+/**
+ * Calculates the magnitude (length) of a vector.
+ *
+ * @param pt - The input vector.
+ * @return The magnitude of the vector.
+ */
+float point2mag(vec3 point){
+ // Square each component of the vector, add them together,
+ // and take the square root of the result
+ return sqrt(point.x*point.x + point.y*point.y + point.z*point.z);
+}
+
+/**
+ * Main function for the fragment shader.
+ */
+void main()
+{
+ // Get the color and depth at the current texture coordinates
+ vec4 color = texture(colorTexture, v_textureCoordinates);
+ vec4 cDepth = texture(depthTexture, v_textureCoordinates);
+
+ // Get the depth and position in eye coordinates
+ float depth = getDepth(cDepth);
+ vec4 positionEC = toEye(v_textureCoordinates, depth);
+
+ // If the depth is at its maximum value, set the fragment color to the texture color and return
+ if(cDepth.r >= 1.0){
+ FragColor = color;
+ return;
+ }
+
+ //check to see if we are within distance of the view target
+ float cameraDistance = length(cameraPosition_WC.xyz - viewPosition_WC.xyz);
+
+ // Get the fragment position in world coordinates
+ vec4 fragPosition_WC = vec4(v_textureCoordinates, 0.0, 1.0);
+
+ if (
+ cDepth.r >= 1.0 ||
+ (exclude_terrain && czm_ellipsoidContainsPoint(ellipsoidInverseRadii, positionEC.xyz))
+ ){
+ FragColor = color;
+ return;
+ }
+
+ // Initialize shadow parameters
+ czm_shadowParameters shadowParameters;
+ shadowParameters.texelStepSize = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.xy;
+ shadowParameters.depthBias = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.z;
+ shadowParameters.normalShadingSmooth = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.w;
+ shadowParameters.darkness = shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.w;
+
+ // Adjust the depth bias
+ shadowParameters.depthBias *= max(depth * 0.01, 1.0);
+
+ // Calculate the direction in eye coordinates
+ vec3 directionEC = normalize(positionEC.xyz - shadowMap_camera_positionEC.xyz);
+
+ // Calculate the dot product of the normal and the negative direction
+ float nDotL = clamp(dot(vec3(1.0), -directionEC), 0.0, 1.0);
+
+ // Calculate the shadow position
+ vec4 shadowPosition = shadowMap_matrix * positionEC;
+ shadowPosition /= shadowPosition.w;
+
+ // If the shadow position is outside the [0, 1] range in any dimension, set the fragment color to the texture color and return
+ if (any(lessThan(shadowPosition.xyz, vec3(0.0))) || any(greaterThan(shadowPosition.xyz, vec3(1.0))))
+ {
+ FragColor = color;
+ return;
+ }
+
+ // If the distance between the coordinates and the viewpoint is greater than the maximum distance, the shadow effect is discarded
+ vec4 lw = czm_inverseView* vec4(shadowMap_camera_positionEC.xyz, 1.0);
+ vec4 vw = czm_inverseView* vec4(positionEC.xyz, 1.0);
+
+ if(distance(lw.xyz,vw.xyz)>view_distance){
+ FragColor = color;
+ return;
+ }
+
+ // Set the shadow parameters
+ shadowParameters.texCoords = shadowPosition.xy;
+ shadowParameters.depth = shadowPosition.z;
+ shadowParameters.nDotL = nDotL;
+
+ // Calculate the shadow visibility
+ float visibility = czm_shadowVisibility(shadowMap, shadowParameters);
+
+ // If the visibility is 1.0, mix the color with the visible color
+ if(visibility==1.0){
+ FragColor = mix(texture(colorTexture, v_textureCoordinates),vec4(viewArea_color,1.0),percentShade);
+ }else{
+ if(abs(shadowPosition.z-0.0)<0.01){
+ FragColor = color;
+ return;
+ }
+ FragColor = mix(texture(colorTexture, v_textureCoordinates),vec4(shadowArea_color,1.0),percentShade);
+ }
+}`;
+const fsShader = fsShaderText.replace('`;', '');
+//https://github.com/DigitalArsenal/SensorShadow
+const {
+ ShadowMap,
+ PerspectiveFrustum,
+ Camera,
+ Color,
+ defaultValue,
+ PositionProperty,
+ ConstantPositionProperty,
+ Cartesian2,
+ Cartesian3,
+ Cartesian4,
+ EllipsoidTerrainProvider,
+ PostProcessStage,
+ Math: CesiumMath
+} = Cesium;
+
+const defaultValues = {
+ cameraPosition: new ConstantPositionProperty(),
+ viewPosition: new ConstantPositionProperty(),
+ viewAreaColor: new Color(0, 1, 0),
+ shadowAreaColor: new Color(1, 0, 0),
+ alpha: 0.5,
+ frustum: true,
+ size: 4096,
+ depthBias: 2e-12
+};
+
+/**
+ * SensorShadow Class.
+ * This class handles the creation, update and management of sensor shadow entities.
+ *
+ * @property {Object} viewer - A reference to the Cesium viewer instance.
+ * @property {ConstantPositionProperty|PositionProperty|Cartesian3} cameraPosition - The camera position.
+ * @property {ConstantPositionProperty|PositionProperty|Cartesian3} viewPosition - The view position.
+ * @property {Color} viewAreaColor - The color of the visible area of the sensor shadow.
+ * @property {Color} shadowAreaColor - The color of the hidden area of the sensor shadow.
+ * @property {number} alpha - The alpha value for the sensor shadow.
+ * @property {boolean} frustum - Whether the frustum is enabled.
+ * @property {number} size - The size of the sensor shadow.
+ * @property {function|null} preUpdateListener - A pre-update listener function.
+ */
+class SensorShadow {
+ /**
+ * Constructs a new SensorShadow instance.
+ *
+ * @param {Object} viewer - A reference to the Cesium viewer instance.
+ * @param {Object} options - An optional configuration object.
+ *
+ * @example
+ * let sensorShadow = new SensorShadow(viewer, {
+ * cameraPosition: new Cartesian3(0, 0, 0),
+ * viewPosition: new Cartesian3(1, 1, 1),
+ * viewAreaColor: new Color(0, 1, 0),
+ * shadowAreaColor: new Color(1, 0, 0),
+ * alpha: 0.5,
+ * frustum: true,
+ * size: 512
+ * });
+ */
+ constructor(viewer, options = {}) {
+ this.viewer = viewer;
+
+ this.cameraPosition =
+ typeof options.cameraPosition.getValue === 'function'
+ ? options.cameraPosition
+ : new ConstantPositionProperty(options.cameraPosition);
+
+ this.viewPosition =
+ typeof options.viewPosition.getValue === 'function'
+ ? options.viewPosition
+ : new ConstantPositionProperty(options.viewPosition);
+
+ this.viewAreaColor = defaultValue(options.viewAreaColor, defaultValues.viewAreaColor);
+
+ this.shadowAreaColor = defaultValue(options.shadowAreaColor, defaultValues.shadowAreaColor);
+
+ this.alpha = defaultValue(options.alpha, defaultValues.alpha);
+ this.size = defaultValue(options.size, defaultValues.size);
+ this.frustum = defaultValue(options.frustum, defaultValues.frustum);
+ this.depthBias = defaultValue(options.depthBias, defaultValues.depthBias);
+
+ this.preUpdateListener = null;
+
+ if (this.cameraPosition && this.viewPosition) {
+ this._addToScene();
+ }
+ }
+
+ /**
+ * Get the actual position of the camera.
+ * This method calculates the position vector based on the current time.
+ *
+ * @private
+ * @returns {Cartesian3} The calculated camera position vector.
+ */
+ get _getVectors() {
+ let positionVector = this.cameraPosition.getValue(this.viewer.clock.currentTime);
+ let viewVector = this.viewPosition.getValue(this.viewer.clock.currentTime);
+ let distanceBetweenVectors = Number(Cartesian3.distance(viewVector, positionVector).toFixed(1));
+
+ if (distanceBetweenVectors > 10000) {
+ let multiple = 1 - 10000 / distanceBetweenVectors;
+ positionVector = Cartesian3.lerp(positionVector, viewVector, multiple, new Cartesian3());
+ }
+
+ return { positionVector, viewVector };
+ }
+
+ /**
+ * Adds the SensorShadow to the scene.
+ *
+ * @private
+ */
+ _addToScene() {
+ this._createShadowMap();
+ this._addPostProcess();
+ this.viewer.scene.primitives.add(this);
+ }
+
+ /**
+ * Creates the shadow map.
+ *
+ * @private
+ */
+ _createShadowMap(updateOnly) {
+ let { positionVector, viewVector } = this._getVectors;
+
+ const distance = Number(Cartesian3.distance(viewVector, positionVector).toFixed(1));
+
+ if (distance > 10000) {
+ const multiple = 1 - 10000 / distance;
+ positionVector = Cartesian3.lerp(positionVector, viewVector, multiple, new Cartesian3());
+ }
+
+ const scene = this.viewer.scene;
+
+ const camera = new Camera(scene);
+
+ camera.position = positionVector;
+
+ camera.direction = Cartesian3.subtract(viewVector, positionVector, new Cartesian3(0, 0, 0));
+
+ camera.up = Cartesian3.normalize(positionVector, new Cartesian3(0, 0, 0));
+
+ camera.frustum = new PerspectiveFrustum({
+ fov: CesiumMath.toRadians(120),
+ aspectRatio: scene.canvas.clientWidth / scene.canvas.clientHeight,
+ near: 0.1,
+ far: distance
+ });
+
+ if (!updateOnly) {
+ this.viewShadowMap = new ShadowMap({
+ lightCamera: camera,
+ enable: true,
+ isPointLight: false,
+ isSpotLight: true,
+ cascadesEnabled: false,
+ context: scene.context,
+ size: this.size,
+ pointLightRadius: distance,
+ fromLightSource: false,
+ maximumDistance: distance
+ });
+ } else {
+ this.viewShadowMap._lightCamera.position = positionVector;
+ }
+
+ this.viewShadowMap.normalOffset = true;
+ this.viewShadowMap._terrainBias.depthBias = 0.0;
+ }
+
+ /**
+ * Adds post processing to the SensorShadow.
+ *
+ * @private
+ */
+ _addPostProcess() {
+ const SensorShadow = this;
+
+ const viewShadowMap = this.viewShadowMap;
+ const primitiveBias = viewShadowMap._isPointLight
+ ? viewShadowMap._pointBias
+ : viewShadowMap._primitiveBias;
+ this.postProcess = this.viewer.scene.postProcessStages.add(
+ new PostProcessStage({
+ fragmentShader: fsShader,
+ uniforms: {
+ view_distance: function () {
+ return SensorShadow.distance;
+ },
+ viewArea_color: function () {
+ return SensorShadow.viewAreaColor;
+ },
+ shadowArea_color: function () {
+ return SensorShadow.shadowAreaColor;
+ },
+ percentShade: function () {
+ return SensorShadow.alpha;
+ },
+ shadowMap: function () {
+ return viewShadowMap._shadowMapTexture;
+ },
+ _shadowMap_cascadeSplits: function () {
+ return viewShadowMap._cascadeSplits;
+ },
+ _shadowMap_cascadeMatrices: function () {
+ return viewShadowMap._cascadeMatrices;
+ },
+ _shadowMap_cascadeDistances: function () {
+ return viewShadowMap._cascadeDistances;
+ },
+ shadowMap_matrix: function () {
+ return viewShadowMap._shadowMapMatrix;
+ },
+ shadowMap_camera_positionEC: function () {
+ return viewShadowMap._lightPositionEC;
+ },
+ shadowMap_camera_directionEC: function () {
+ return viewShadowMap._lightDirectionEC;
+ },
+ cameraPosition_WC: function () {
+ return SensorShadow.viewer.camera.positionWC;
+ },
+ viewPosition_WC: function () {
+ return SensorShadow.viewPosition.getValue(SensorShadow.viewer.clock.currentTime);
+ },
+ shadowMap_camera_up: function () {
+ return viewShadowMap._lightCamera.up;
+ },
+ shadowMap_camera_dir: function () {
+ return viewShadowMap._lightCamera.direction;
+ },
+ shadowMap_camera_right: function () {
+ return viewShadowMap._lightCamera.right;
+ },
+ ellipsoidInverseRadii: function () {
+ let radii = SensorShadow.viewer.scene.globe.ellipsoid.radii;
+ return new Cartesian3(1 / radii.x, 1 / radii.y, 1 / radii.z);
+ },
+ shadowMap_texelSizeDepthBiasAndNormalShadingSmooth: function () {
+ var viewShed2D = new Cartesian2();
+ viewShed2D.x = 1 / viewShadowMap._textureSize.x;
+ viewShed2D.y = 1 / viewShadowMap._textureSize.y;
+
+ return Cartesian4.fromElements(
+ viewShed2D.x,
+ viewShed2D.y,
+ this.depthBias,
+ primitiveBias.normalShadingSmooth,
+ this.combinedUniforms1
+ );
+ },
+ shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness: function () {
+ return Cartesian4.fromElements(
+ primitiveBias.normalOffsetScale,
+ viewShadowMap._distance,
+ viewShadowMap.maximumDistance,
+ viewShadowMap._darkness,
+ this.combinedUniforms2
+ );
+ },
+ exclude_terrain: function () {
+ return SensorShadow.viewer.terrainProvider instanceof EllipsoidTerrainProvider;
+ }
+ }
+ })
+ );
+
+ // If a previous listener was added, remove it
+ if (this.preUpdateListener) {
+ viewer.scene.preUpdate.removeEventListener(this.preUpdateListener);
+ }
+
+ // Add a new listener
+ this.preUpdateListener = () => {
+ if (!this.viewShadowMap._shadowMapTexture) {
+ this.postProcess.enabled = false;
+ } else {
+ this.postProcess.enabled = true;
+ }
+ };
+
+ viewer.scene.preUpdate.addEventListener(this.preUpdateListener);
+ }
+
+ update(frameState) {
+ this._createShadowMap(true);
+ frameState.shadowMaps.push(this.viewShadowMap);
+ }
+
+ destroy() {
+ if (this.preUpdateListener) {
+ viewer.scene.preUpdate.removeEventListener(this.preUpdateListener);
+ }
+ this.viewer.scene.postProcessStages.remove(this.postProcess);
+ for (let property in this) {
+ if (this.hasOwnProperty(property)) {
+ delete this[property];
+ }
+ }
+ }
+
+ get size() {
+ return this._size;
+ }
+
+ set size(v) {
+ this._size = v;
+ }
+
+ get depthBias() {
+ return this._depthBias;
+ }
+
+ set depthBias(v) {
+ this._depthBias = v;
+ }
+
+ get cameraPosition() {
+ return this._cameraPosition;
+ }
+
+ set cameraPosition(v) {
+ this._cameraPosition = v;
+ }
+
+ get viewPosition() {
+ return this._viewPosition;
+ }
+
+ set viewPosition(v) {
+ this._viewPosition = v;
+ }
+
+ get frustum() {
+ return this._frustum;
+ }
+
+ set frustum(v) {
+ this._frustum = v;
+ }
+
+ get distance() {
+ return this._distance;
+ }
+
+ set distance(v) {
+ this._distance = v;
+ }
+
+ get viewAreaColor() {
+ return this._viewAreaColor;
+ }
+
+ set viewAreaColor(v) {
+ this._viewAreaColor = v;
+ }
+
+ get shadowAreaColor() {
+ return this._shadowAreaColor;
+ }
+
+ set shadowAreaColor(v) {
+ this._shadowAreaColor = v;
+ }
+
+ get alpha() {
+ return this._alpha;
+ }
+
+ set alpha(v) {
+ this._alpha = v;
+ }
+}
+
+let pointA = Cesium.Cartesian3.fromDegrees(35.198213, 33.264289, 250); // Central Park
+
+let pointB = Cesium.Cartesian3.fromDegrees(35.200014, 33.268811, 40); // Empire State Building
+
+let viewer;
+Promise.all([
+ fetchWmtsTileTemplate(RASTER_PRODUCT_ID, RASTER_PRODUCT_TYPE, LAYER_IMAGE_FORMAT),
+ fetchServiceLink('dem', DEM_PRODUCT_ID, DEM_PRODUCT_TYPE, DEM_SCHEME),
+ fetchServiceLink('3d', MODEL_3D_PRODUCT_ID, MODEL_3D_PRODUCT_TYPE, MODEL_3D_SCHEME)
+]).then(([raster, dem, model]) => {
+ viewer = new Cesium.Viewer('cesiumContainer', {
+ baseLayer: new Cesium.ImageryLayer(
+ new Cesium.WebMapTileServiceImageryProvider({
+ url: new Cesium.Resource({
+ url: raster.template,
+ queryParameters: {
+ token: TOKEN
+ }
+ }),
+ layer: raster.name,
+ style: 'default',
+ format: LAYER_IMAGE_FORMAT,
+ tileMatrixSetID: 'WorldCRS84',
+ tilingScheme: new Cesium.GeographicTilingScheme()
+ })
+ ),
+ terrainProvider: new Cesium.CesiumTerrainProvider({
+ url: new Cesium.Resource({
+ url: dem.url,
+ queryParameters: {
+ token: TOKEN
+ }
+ })
+ })
+ });
+
+ viewer.scene.primitives.add(
+ new Cesium.Cesium3DTileset({
+ url: new Cesium.Resource({
+ url: model.url,
+ queryParameters: {
+ token: TOKEN
+ }
+ }),
+ maximumScreenSpaceError: 5,
+ cullRequestsWhileMovingMultiplier: 120,
+ preloadFlightDestination: true,
+ preferLeaves: true,
+ skipLevelOfDetail: true
+ })
+ );
+
+ viewer.camera.flyTo({
+ destination: pointA,
+ orientation: {
+ heading: Cesium.Math.toRadians(25.0),
+ pitch: Cesium.Math.toRadians(-10.0),
+ roll: 0.0
+ }
+ });
+ viewer.camera.moveEnd.addEventListener(function () {
+ const { camera } = viewer;
+ if (camera.position?.clone) {
+ const cameraState = {
+ position: camera.position.clone(),
+ direction: camera.direction.clone(),
+ up: camera.up.clone()
+ };
+ }
+ });
+
+ viewer.clock.shouldAnimate = false;
+ //return;
+ const redBall = viewer.entities.add({
+ position: pointA,
+ point: {
+ pixelSize: 10,
+ color: Cesium.Color.RED
+ }
+ });
+
+ //return;
+ const blueBall = viewer.entities.add({
+ position: pointB,
+ point: {
+ pixelSize: 10,
+ color: Cesium.Color.BLUE
+ }
+ });
+
+ //@ts-ignore
+ var sensorShadowInstance = new SensorShadow(viewer, {
+ cameraPosition: redBall.position,
+ viewPosition: pointB
+ });
+
+ let handler;
+ let pickedEntity;
+ let cartesian;
+ handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
+ handler.setInputAction((click) => {
+ cartesian = viewer.camera.pickEllipsoid(click.position, viewer.scene.globe.ellipsoid);
+ if (cartesian) {
+ var pickedObject = viewer.scene.pick(click.position);
+ if (Cesium.defined(pickedObject) && pickedObject.id === redBall) {
+ pickedEntity = pickedObject.id;
+ viewer.scene.screenSpaceCameraController.enableInputs = false;
+ }
+ }
+ }, Cesium.ScreenSpaceEventType.LEFT_DOWN);
+
+ handler.setInputAction((movement) => {
+ if (pickedEntity) {
+ let newCartesian = viewer.camera.pickEllipsoid(
+ movement.endPosition,
+ viewer.scene.globe.ellipsoid
+ );
+ if (newCartesian) {
+ // Convert the picked Cartesian3 to Cartographic
+ let newCartographic = Cesium.Cartographic.fromCartesian(newCartesian);
+
+ // Get the original height
+ let originalCartographic = Cesium.Cartographic.fromCartesian(
+ pickedEntity.position.getValue(Cesium.JulianDate.now())
+ );
+
+ // Update the height to the original one
+ newCartographic.height = originalCartographic.height;
+
+ // Convert the updated Cartographic back to Cartesian3
+ let updatedCartesian = Cesium.Cartographic.toCartesian(newCartographic);
+
+ // Set the new position
+ pickedEntity.position = new Cesium.ConstantPositionProperty(updatedCartesian);
+ sensorShadowInstance.cameraPosition = pickedEntity.position;
+ }
+ }
+ }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
+
+ handler.setInputAction(() => {
+ if (pickedEntity) {
+ pickedEntity = undefined;
+ viewer.scene.screenSpaceCameraController.enableInputs = true;
+ }
+ }, Cesium.ScreenSpaceEventType.LEFT_UP);
+});
diff --git a/examples/cesium-wmts/cesium.html b/examples/cesium-wmts/cesium.html
new file mode 100644
index 0000000..95872da
--- /dev/null
+++ b/examples/cesium-wmts/cesium.html
@@ -0,0 +1 @@
+
diff --git a/examples/cesium-wmts/cesium.js b/examples/cesium-wmts/cesium.js
new file mode 100644
index 0000000..af72d81
--- /dev/null
+++ b/examples/cesium-wmts/cesium.js
@@ -0,0 +1,23 @@
+import { TOKEN } from './config/common-config.js';
+import { PRODUCT_ID, PRODUCT_TYPE, LAYER_IMAGE_FORMAT } from './config/raster-config.js';
+import { fetchWmtsTileTemplate } from './utils/wmts-utils.js';
+
+fetchWmtsTileTemplate(PRODUCT_ID, PRODUCT_TYPE, LAYER_IMAGE_FORMAT).then(({ template, name }) => {
+ new Cesium.Viewer('cesiumContainer', {
+ baseLayer: new Cesium.ImageryLayer(
+ new Cesium.WebMapTileServiceImageryProvider({
+ url: new Cesium.Resource({
+ url: template,
+ queryParameters: {
+ token: TOKEN
+ }
+ }),
+ layer: name,
+ style: 'default',
+ format: LAYER_IMAGE_FORMAT,
+ tileMatrixSetID: 'WorldCRS84',
+ tilingScheme: new Cesium.GeographicTilingScheme()
+ })
+ )
+ });
+});
diff --git a/examples/config/3d-config.js b/examples/config/3d-config.js
new file mode 100644
index 0000000..a37e778
--- /dev/null
+++ b/examples/config/3d-config.js
@@ -0,0 +1,6 @@
+import { MAPCOLONIES_TILES_URL } from './config/common-config.js';
+
+export var MODEL_3D_SCHEME = '3DTiles';
+export var PRODUCT_ID = 'd03ee59f-1676-4059-84cb-a0f68f15aefe';
+export var PRODUCT_TYPE = '3DPhotoRealistic';
+export var MODEL_3D_URL = `${MAPCOLONIES_TILES_URL}/api/3d/v1/b3dm/${PRODUCT_ID}/tileset.json`;
diff --git a/examples/config/common-config.js b/examples/config/common-config.js
new file mode 100644
index 0000000..3f60bf9
--- /dev/null
+++ b/examples/config/common-config.js
@@ -0,0 +1,5 @@
+export var MAPCOLONIES_TILES_URL = 'TILES_URL';
+export var MAPCOLONIES_QUERY_URL = 'QUERY_URL';
+export var MAPCOLONIES_GEOCODING_URL = 'GEOCODING_URL';
+export var MAPCOLONIES_CATALOG_URL = 'CATALOG_URL';
+export var TOKEN = 'TOKEN';
diff --git a/examples/config/dem-config.js b/examples/config/dem-config.js
new file mode 100644
index 0000000..a8159de
--- /dev/null
+++ b/examples/config/dem-config.js
@@ -0,0 +1,6 @@
+import { MAPCOLONIES_TILES_URL } from './config/common-config.js';
+
+export var DEM_SCHEME = 'WCS';
+export var PRODUCT_ID = 'srtm_100_30-aoi';
+export var PRODUCT_TYPE = 'DTM';
+export var DEM_URL = `${MAPCOLONIES_TILES_URL}/api/dem/v1/terrains/${PRODUCT_ID}`;
diff --git a/examples/config/raster-config.js b/examples/config/raster-config.js
new file mode 100644
index 0000000..ed42d8c
--- /dev/null
+++ b/examples/config/raster-config.js
@@ -0,0 +1,13 @@
+import { MAPCOLONIES_TILES_URL, TOKEN } from './config/common-config.js';
+
+var WMTS_BASE_URL = `${MAPCOLONIES_TILES_URL}/api/raster/v1/wmts`;
+
+export var RASTER_SCHEME = 'WMTS';
+export var PRODUCT_ID = 'blueMarble';
+export var PRODUCT_TYPE = 'Orthophoto';
+export var LAYER_NAME = `${PRODUCT_ID}-${PRODUCT_TYPE}`;
+export var ADDITIONAL_LAYER_NAME = `${PRODUCT_ID}-${PRODUCT_TYPE}`;
+export var LAYER_IMAGE_FORMAT = 'image/png';
+export var RASTER_SERVICE_URL = `${MAPCOLONIES_TILES_URL}/api/raster/v1/service`;
+export var WMTS_CAPABILITIES_URL = `${WMTS_BASE_URL}/1.0.0/WMTSCapabilities.xml?token=${TOKEN}`;
+export var WMTS_URL = `${WMTS_BASE_URL}/${LAYER_NAME}/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}.jpeg`;
diff --git a/examples/config/vector-config.js b/examples/config/vector-config.js
new file mode 100644
index 0000000..6ec23a7
--- /dev/null
+++ b/examples/config/vector-config.js
@@ -0,0 +1,4 @@
+import { MAPCOLONIES_QUERY_URL, MAPCOLONIES_GEOCODING_URL } from './config/common-config.js';
+
+export var VECTOR_WFS_URL = `${MAPCOLONIES_QUERY_URL}/api/vector/v1/core/wfs`;
+export var GEOCODING_URL = `${MAPCOLONIES_GEOCODING_URL}/api/osm/v1/search`;
diff --git a/examples/index.json b/examples/index.json
new file mode 100644
index 0000000..40b1734
--- /dev/null
+++ b/examples/index.json
@@ -0,0 +1,384 @@
+{
+ "OpenLayers": {
+ "WMTS": {
+ "displayName": "WMTS",
+ "description": "Loads a MapColonies raster layer into OpenLayers via the WMTS capabilities document. Shows how to authenticate with a token, parse capabilities, and build a tile source.\n\nUse this as the starting point for any 2D map backed by MapColonies tiles — it covers the auth + capabilities handshake you would otherwise have to wire up yourself.",
+ "files": [
+ "openlayers-wmts/wmts.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/xml-utils.js",
+ "openlayers-wmts/openlayers.css",
+ "openlayers-wmts/openlayers.html"
+ ],
+ "links": [
+ { "name": "ol.css", "url": "/libs/ol/v7.3.0/ol.css", "type": "css" },
+ { "name": "ol.js", "url": "/libs/ol/v7.3.0/ol.js", "type": "js" }
+ ]
+ },
+ "OverviewMap": {
+ "displayName": "OverviewMap(MiniMap)",
+ "description": "Adds an OpenLayers OverviewMap control — a minimap in the corner that mirrors the main view.\n\nGives users spatial context when zoomed in deeply, without you having to build a synced second map by hand.",
+ "files": [
+ "openlayers-overview-map/overview-map.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/xml-utils.js",
+ "openlayers-overview-map/overview-map.css",
+ "openlayers-overview-map/overview-map.html"
+ ],
+ "links": [
+ { "name": "ol.css", "url": "/libs/ol/v7.3.0/ol.css", "type": "css" },
+ { "name": "ol.js", "url": "/libs/ol/v7.3.0/ol.js", "type": "js" }
+ ]
+ },
+ "Permalink": {
+ "displayName": "Permalink",
+ "description": "Keeps the map center, zoom and rotation in sync with the URL hash, and restores them on reload.\n\nLets users share a deep-link to any view and bookmark map state — no backend persistence needed.",
+ "files": [
+ "openlayers-permalink/permalink.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/xml-utils.js",
+ "openlayers-permalink/permalink.css",
+ "openlayers-permalink/permalink.html"
+ ],
+ "links": [
+ { "name": "ol.css", "url": "/libs/ol/v7.3.0/ol.css", "type": "css" },
+ { "name": "ol.js", "url": "/libs/ol/v7.3.0/ol.js", "type": "js" }
+ ]
+ },
+ "QueryService": {
+ "displayName": "QueryService",
+ "description": "Queries the MapColonies vector WFS endpoint and renders matching features on top of the raster basemap.\n\nShows the request shape and response handling so you can filter and display server-side data without writing the WFS plumbing yourself.",
+ "files": [
+ "openlayers-query-service/query-service.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/xml-utils.js",
+ "config/vector-config.js",
+ "openlayers-query-service/openlayers.css",
+ "openlayers-query-service/openlayers.html"
+ ],
+ "links": [
+ { "name": "ol.css", "url": "/libs/ol/v7.3.0/ol.css", "type": "css" },
+ { "name": "ol.js", "url": "/libs/ol/v7.3.0/ol.js", "type": "js" }
+ ]
+ },
+ "InteractiveFeatureSelect": {
+ "displayName": "Interactive Feature Select",
+ "description": "Drag-box selection over vector features, with hit testing and styled selection state.\n\nDrop-in pattern for bulk operations (export, edit, delete) on map features — saves you from reinventing the box-drag + selection bookkeeping.",
+ "files": [
+ "openlayers-box-selection/box-selection.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/xml-utils.js",
+ "config/vector-config.js",
+ "openlayers-box-selection/openlayers.css",
+ "openlayers-box-selection/box-selection.html"
+ ],
+ "links": [
+ { "name": "ol.css", "url": "/libs/ol/v7.3.0/ol.css", "type": "css" },
+ { "name": "ol.js", "url": "/libs/ol/v7.3.0/ol.js", "type": "js" }
+ ]
+ },
+ "InteractiveSensitiveBuildings": {
+ "displayName": "Interactive Sensitive Buldings Select",
+ "description": "Loads sensitive building features, highlights them on hover, and exposes attribute data on click.\n\nReference for domain-specific POI workflows where styling, hit detection and attribute panels need to work together.",
+ "files": [
+ "openlayers-sensitive/interactive-sensitive.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/xml-utils.js",
+ "config/vector-config.js",
+ "openlayers-sensitive/openlayers.css",
+ "openlayers-sensitive/openlayers.html"
+ ],
+ "links": [
+ { "name": "ol.css", "url": "/libs/ol/v7.3.0/ol.css", "type": "css" },
+ { "name": "ol.js", "url": "/libs/ol/v7.3.0/ol.js", "type": "js" }
+ ]
+ },
+ "Debug Layer": {
+ "displayName": "DebugLayer",
+ "description": "Overlays the WMTS tile grid with tile coordinates and matrix IDs.\n\nUse during integration to verify that your tile matrix set, origin and resolutions actually line up — turns 'why is my map blank' into a visible diagnosis.",
+ "files": [
+ "openlayers-debug-layer/debug-layer.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/xml-utils.js",
+ "openlayers-debug-layer/openlayers.css",
+ "openlayers-debug-layer/openlayers.html"
+ ],
+ "links": [
+ { "name": "ol.css", "url": "/libs/ol/v7.3.0/ol.css", "type": "css" },
+ { "name": "ol.js", "url": "/libs/ol/v7.3.0/ol.js", "type": "js" }
+ ]
+ },
+ "DrawAndModify": {
+ "displayName": "Draw and Modify Geodesic",
+ "description": "Draw and edit points, lines and polygons with geodesic geometry — distances and areas are computed on the ellipsoid, not the projected plane.\n\nIf you need measurement-correct sketching (AOIs, range rings, routes), this saves you from the classic 'planar distance looks wrong at high latitudes' bug.",
+ "files": [
+ "openlayers-draw-and-modify/draw-and-modify.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/xml-utils.js",
+ "config/vector-config.js",
+ "openlayers-draw-and-modify/draw-and-modify.css",
+ "openlayers-draw-and-modify/draw-and-modify.html"
+ ],
+ "links": [
+ { "name": "ol.css", "url": "/libs/ol/v7.3.0/ol.css", "type": "css" },
+ { "name": "ol.js", "url": "/libs/ol/v7.3.0/ol.js", "type": "js" }
+ ]
+ },
+ "PreloadTiles": {
+ "displayName": "Preload Tiles",
+ "description": "Configures the tile layer to preload neighbouring zoom levels in the background.\n\nResult: pan and zoom feel snappier because the next tiles are already in cache. Useful when you need a smooth UX on slow networks without writing a custom tile cache.",
+ "files": [
+ "openlayers-preload/preload.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/xml-utils.js",
+ "openlayers-preload/preload.css",
+ "openlayers-preload/preload.html"
+ ],
+ "links": [
+ { "name": "ol.css", "url": "/libs/ol/v7.3.0/ol.css", "type": "css" },
+ { "name": "ol.js", "url": "/libs/ol/v7.3.0/ol.js", "type": "js" }
+ ]
+ },
+ "DrawStreetLabels": {
+ "displayName": "Draw Street Labels",
+ "description": "Renders street-name labels from a vector source with collision avoidance and rotation-aware placement.\n\nShortcut to readable labelling on top of raster basemaps — the placement/rotation logic is where naive implementations usually fall over.",
+ "files": [
+ "openlayers-street-labels/street-labels.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/xml-utils.js",
+ "config/vector-config.js",
+ "openlayers-street-labels/street-labels.css",
+ "openlayers-street-labels/street-labels.html"
+ ],
+ "links": [
+ { "name": "ol.css", "url": "/libs/ol/v7.3.0/ol.css", "type": "css" },
+ { "name": "ol.js", "url": "/libs/ol/v7.3.0/ol.js", "type": "js" }
+ ]
+ },
+ "ol-ext": {
+ "displayName": "ol-ext",
+ "description": "Integrates the ol-ext extension library on top of the MapColonies WMTS basemap, showcasing ready-made controls and interactions.\n\nUse it as a survey of off-the-shelf widgets (legends, layer switchers, popups) so you don't end up reimplementing common UI from scratch.",
+ "files": [
+ "ol-ext-example/ol-ext.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/xml-utils.js",
+ "ol-ext-example/openlayers.css",
+ "ol-ext-example/openlayers.html"
+ ],
+ "links": [
+ { "name": "ol.css", "url": "/libs/ol/v7.3.0/ol.css", "type": "css" },
+ { "name": "ol.js", "url": "/libs/ol/v7.3.0/ol.js", "type": "js" }
+ ]
+ }
+ },
+ "Cesium": {
+ "WMTS": {
+ "displayName": "WMTS Imagery Provider",
+ "description": "Wires a MapColonies WMTS raster layer into Cesium as a WebMapTileServiceImageryProvider, using the capabilities document to pick the right tile matrix set.\n\nStart here for any 3D globe view backed by MapColonies imagery — the capabilities + tilingScheme matching is the part most integrators get wrong.",
+ "files": [
+ "cesium-wmts/cesium.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/wmts-utils.js",
+ "utils/xml-utils.js",
+ "cesium-wmts/cesium.html"
+ ],
+ "links": [
+ {
+ "name": "cesium.js",
+ "url": "/libs/Cesium/Cesium.js",
+ "type": "js"
+ },
+ {
+ "name": "widgets.css",
+ "url": "/libs/Cesium/Widgets/widgets.css",
+ "type": "css"
+ }
+ ]
+ },
+ "GeoCoding": {
+ "displayName": "GeoCoding",
+ "description": "Sends user-typed queries to the MapColonies geocoding service and flies the Cesium camera to matching results.\n\nDrop-in 'search this place' UX for 3D viewers — covers request shape, result ranking and camera animation in one example.",
+ "files": [
+ "cesium-geocoding/cesium.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/wmts-utils.js",
+ "utils/xml-utils.js",
+ "config/vector-config.js",
+ "cesium-geocoding/cesium.html"
+ ],
+ "links": [
+ {
+ "name": "cesium.js",
+ "url": "/libs/Cesium/Cesium.js",
+ "type": "js"
+ },
+ {
+ "name": "widgets.css",
+ "url": "/libs/Cesium/Widgets/widgets.css",
+ "type": "css"
+ }
+ ]
+ },
+ "Terrain": {
+ "displayName": "Terrain Provider",
+ "description": "Attaches a MapColonies DEM as a Cesium terrain provider so the globe has real elevation underneath the imagery.\n\nGives you photorealistic relief and correct picking heights without having to host or tile your own DEM.",
+ "files": [
+ "cesium-terrain/cesium.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/wmts-utils.js",
+ "utils/xml-utils.js",
+ "config/dem-config.js",
+ "cesium-terrain/cesium.html"
+ ],
+ "links": [
+ {
+ "name": "cesium.js",
+ "url": "/libs/Cesium/Cesium.js",
+ "type": "js"
+ },
+ {
+ "name": "widgets.css",
+ "url": "/libs/Cesium/Widgets/widgets.css",
+ "type": "css"
+ }
+ ]
+ },
+ "3D Model": {
+ "displayName": "3D Model (South Lebanon)",
+ "description": "Composes the full MapColonies 3D stack: WMTS imagery, DEM terrain and a photo-realistic 3D Tiles mesh, all aligned in one scene.\n\nReference for end-to-end 3D scene assembly — shows the loading order and provider config that makes imagery, terrain and meshes line up correctly.",
+ "files": [
+ "cesium-3d/3d-model.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/wmts-utils.js",
+ "utils/xml-utils.js",
+ "config/3d-config.js",
+ "config/dem-config.js",
+ "cesium-3d/cesium.html"
+ ],
+ "links": [
+ {
+ "name": "cesium.js",
+ "url": "/libs/Cesium/Cesium.js",
+ "type": "js"
+ },
+ {
+ "name": "widgets.css",
+ "url": "/libs/Cesium/Widgets/widgets.css",
+ "type": "css"
+ }
+ ]
+ },
+ "LayersSplit": {
+ "displayName": "Layers Split",
+ "description": "Splits the Cesium view between two imagery layers with a draggable divider — left side shows layer A, right side shows layer B.\n\nReady-made before/after compare UX for change detection, version review or A/B imagery without writing the splitter math yourself.",
+ "files": [
+ "cesium-layers-split/split-layers.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/wmts-utils.js",
+ "utils/xml-utils.js",
+ "cesium-layers-split/split-layers.html",
+ "cesium-layers-split/split-layers.css"
+ ],
+ "links": [
+ {
+ "name": "cesium.js",
+ "url": "/libs/Cesium/Cesium.js",
+ "type": "js"
+ },
+ {
+ "name": "widgets.css",
+ "url": "/libs/Cesium/Widgets/widgets.css",
+ "type": "css"
+ }
+ ]
+ },
+ "ViewShed": {
+ "displayName": "ViewShed",
+ "description": "Computes a viewshed (visible / not visible) from an observer point over the 3D Tiles mesh and DEM, using a custom shader.\n\nLine-of-sight analysis for planning, security and coverage use cases — the shader plus depth-buffer trick is the hard part this example does for you.",
+ "files": [
+ "cesium-viewshed/viewshed.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/wmts-utils.js",
+ "utils/xml-utils.js",
+ "config/3d-config.js",
+ "config/dem-config.js",
+ "cesium-viewshed/viewshed.html",
+ "cesium-viewshed/viewshed.css"
+ ],
+ "links": [
+ {
+ "name": "cesium.js",
+ "url": "/libs/Cesium/Cesium.js",
+ "type": "js"
+ },
+ {
+ "name": "widgets.css",
+ "url": "/libs/Cesium/Widgets/widgets.css",
+ "type": "css"
+ }
+ ]
+ }
+ },
+ "Leaflet": {
+ "WMTS": {
+ "displayName": "Basic WMTS Example",
+ "description": "Minimal Leaflet map consuming the MapColonies WMTS service via a plain L.tileLayer.\n\nGood entry point when you want a lightweight 2D map without OpenLayers — shows the URL template and token handling needed to hit the same backend.",
+ "files": [
+ "leaflet-wmts/wmts.js",
+ "config/common-config.js",
+ "config/raster-config.js",
+ "utils/catalog-client.js",
+ "utils/wmts-utils.js",
+ "utils/xml-utils.js",
+ "leaflet-wmts/leaflet.css",
+ "leaflet-wmts/leaflet.html"
+ ],
+ "links": [
+ {
+ "name": "leaflet.js",
+ "url": "/libs/leaflet/1.9.4/leaflet.js",
+ "type": "js"
+ },
+ {
+ "name": "leaflet.css",
+ "url": "/libs/leaflet/1.9.4/leaflet.css",
+ "type": "css"
+ }
+ ]
+ }
+ }
+}
diff --git a/examples/leaflet-wmts/leaflet.css b/examples/leaflet-wmts/leaflet.css
new file mode 100644
index 0000000..63c0413
--- /dev/null
+++ b/examples/leaflet-wmts/leaflet.css
@@ -0,0 +1,3 @@
+#map {
+ height: 100%;
+}
diff --git a/examples/leaflet-wmts/leaflet.html b/examples/leaflet-wmts/leaflet.html
new file mode 100644
index 0000000..ad19e7d
--- /dev/null
+++ b/examples/leaflet-wmts/leaflet.html
@@ -0,0 +1 @@
+
diff --git a/examples/leaflet-wmts/wmts.js b/examples/leaflet-wmts/wmts.js
new file mode 100644
index 0000000..180cbe3
--- /dev/null
+++ b/examples/leaflet-wmts/wmts.js
@@ -0,0 +1,19 @@
+import { TOKEN } from './config/common-config.js';
+import { PRODUCT_ID, PRODUCT_TYPE } from './config/raster-config.js';
+import { fetchWmtsTileTemplate } from './utils/wmts-utils.js';
+
+const parser = (urlTemplate) => {
+ return urlTemplate
+ .replace('{TileMatrixSet}', 'WorldCRS84')
+ .replace('{TileMatrix}', '{z}')
+ .replace('{TileRow}', '{y}')
+ .replace('{TileCol}', '{x}');
+};
+
+const map = L.map('map', { crs: L.CRS.EPSG4326 }).setView([0.0, 0.0], 1);
+
+fetchWmtsTileTemplate(PRODUCT_ID, PRODUCT_TYPE).then(({ template, name }) => {
+ const parsedUrl = parser(template);
+ const layer = L.tileLayer(parsedUrl + `?token=${TOKEN}`, { id: name });
+ map.addLayer(layer);
+});
diff --git a/examples/ol-ext-example/ol-ext.js b/examples/ol-ext-example/ol-ext.js
new file mode 100644
index 0000000..153ddbb
--- /dev/null
+++ b/examples/ol-ext-example/ol-ext.js
@@ -0,0 +1,32 @@
+import { TOKEN } from './config/common-config.js';
+import { PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME } from './config/raster-config.js';
+import { fetchServiceLink } from './utils/catalog-client.js';
+
+const map = new ol.Map({
+ target: 'map',
+ view: new ol.View({
+ center: [0, 0],
+ zoom: 0,
+ projection: 'EPSG:4326'
+ })
+});
+
+const WMTSParser = new ol.format.WMTSCapabilities();
+
+fetchServiceLink('raster', PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME)
+ .then(({ url, name }) =>
+ fetch(`${url}?token=${TOKEN}`)
+ .then((response) => response.text())
+ .then((text) => ({ text, name }))
+ )
+ .then(({ text, name }) => {
+ const results = WMTSParser.read(text);
+ const options = ol.source.WMTS.optionsFromCapabilities(results, {
+ layer: name
+ });
+ options.urls = options.urls.map((url) => {
+ return url.concat(`?token=${TOKEN}`);
+ });
+ const layer = new ol.layer.Tile({ opacity: 1, source: new ol.source.WMTS(options) });
+ map.addLayer(layer);
+ });
diff --git a/examples/ol-ext-example/openlayers.css b/examples/ol-ext-example/openlayers.css
new file mode 100644
index 0000000..3e6b562
--- /dev/null
+++ b/examples/ol-ext-example/openlayers.css
@@ -0,0 +1,6 @@
+#map {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+}
diff --git a/examples/ol-ext-example/openlayers.html b/examples/ol-ext-example/openlayers.html
new file mode 100644
index 0000000..ad19e7d
--- /dev/null
+++ b/examples/ol-ext-example/openlayers.html
@@ -0,0 +1 @@
+
diff --git a/examples/openlayers-box-selection/box-selection.html b/examples/openlayers-box-selection/box-selection.html
new file mode 100644
index 0000000..e5bbe55
--- /dev/null
+++ b/examples/openlayers-box-selection/box-selection.html
@@ -0,0 +1,21 @@
+
+
+
+
Using a DragBox interaction to select features.
+
+
+ This example shows how to use a DragBox interaction to select features.
+ Selected features are added to the feature overlay of a select interaction
+ (ol/interaction/Select) for highlighting.
+
+
Use Ctrl+Drag (Command+Drag on Mac) to draw boxes.
+
+
+
+
+
+ The selected Features are
+
+
+
+
diff --git a/examples/openlayers-box-selection/box-selection.js b/examples/openlayers-box-selection/box-selection.js
new file mode 100644
index 0000000..10f3896
--- /dev/null
+++ b/examples/openlayers-box-selection/box-selection.js
@@ -0,0 +1,169 @@
+import { TOKEN } from './config/common-config.js';
+import { PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME } from './config/raster-config.js';
+import { fetchServiceLink } from './utils/catalog-client.js';
+
+const WMTSParser = new ol.format.WMTSCapabilities();
+
+const vectorSource = new ol.source.Vector({
+ format: new ol.format.GeoJSON(),
+ strategy: ol.loadingstrategy.tile(ol.tilegrid.createXYZ({ tileSize: 256 })),
+ url: function (extent) {
+ return (
+ VECTOR_WFS_URL +
+ `?service=WFS&version=2.0.0&request=GetFeature&typeName=core:buildings_polygon&srsname=EPSG:4326&bbox=${extent.join(
+ ','
+ )},EPSG:4326&token=${TOKEN}&outputFormat=application/json`
+ );
+ }
+});
+
+const style = new ol.style.Style({
+ fill: new ol.style.Fill({
+ color: '#eeeeee'
+ })
+});
+
+const map = new ol.Map({
+ target: 'map',
+ view: new ol.View({
+ center: [34.465798, 31.513991],
+ zoom: 18,
+ projection: 'EPSG:4326',
+ constrainRotation: 16
+ })
+});
+
+const vectorLayer = new ol.layer.Vector({
+ source: vectorSource,
+ style: function (feature) {
+ const color = feature.get('is_sensitive') === true ? 'red' : '#eeeeee';
+ style.getFill().setColor(color);
+ return style;
+ }
+});
+
+const selectedStyle = new ol.style.Style({
+ fill: new ol.style.Fill({
+ color: 'rgba(180, 2, 180, 0.3)'
+ }),
+ stroke: new ol.style.Stroke({
+ color: 'rgba(180, 2, 180, 0.4)',
+ width: 5
+ })
+});
+
+// a normal select interaction to handle click
+const select = new ol.interaction.Select({
+ style: function (feature) {
+ const color = feature.get('is_sensitive') === true ? 'red' : '#eeeeee';
+ selectedStyle.getFill().setColor(color);
+ return selectedStyle;
+ }
+});
+map.addInteraction(select);
+
+const selectedFeatures = select.getFeatures();
+
+// a DragBox interaction used to select features by drawing boxes
+const dragBox = new ol.interaction.DragBox({
+ condition: ol.events.condition.platformModifierKeyOnly
+});
+
+map.addInteraction(dragBox);
+
+dragBox.on('boxend', function () {
+ const boxExtent = dragBox.getGeometry().getExtent();
+
+ // if the extent crosses the antimeridian process each world separately
+ const worldExtent = map.getView().getProjection().getExtent();
+ const worldWidth = ol.extent.getWidth(worldExtent);
+ const startWorld = Math.floor((boxExtent[0] - worldExtent[0]) / worldWidth);
+ const endWorld = Math.floor((boxExtent[2] - worldExtent[0]) / worldWidth);
+
+ for (let world = startWorld; world <= endWorld; ++world) {
+ const left = Math.max(boxExtent[0] - world * worldWidth, worldExtent[0]);
+ const right = Math.min(boxExtent[2] - world * worldWidth, worldExtent[2]);
+ const extent = [left, boxExtent[1], right, boxExtent[3]];
+
+ const boxFeatures = vectorSource
+ .getFeaturesInExtent(extent)
+ .filter(
+ (feature) =>
+ !selectedFeatures.getArray().includes(feature) &&
+ feature.getGeometry().intersectsExtent(extent)
+ );
+
+ // features that intersect the box geometry are added to the
+ // collection of selected features
+
+ // if the view is not obliquely rotated the box geometry and
+ // its extent are equalivalent so intersecting features can
+ // be added directly to the collection
+ const rotation = map.getView().getRotation();
+ const oblique = rotation % (Math.PI / 2) !== 0;
+
+ // when the view is obliquely rotated the box extent will
+ // exceed its geometry so both the box and the candidate
+ // feature geometries are rotated around a common anchor
+ // to confirm that, with the box geometry aligned with its
+ // extent, the geometries intersect
+ if (oblique) {
+ const anchor = [0, 0];
+ const geometry = dragBox.getGeometry().clone();
+ geometry.translate(-world * worldWidth, 0);
+ geometry.rotate(-rotation, anchor);
+ const extent = geometry.getExtent();
+ boxFeatures.forEach(function (feature) {
+ const geometry = feature.getGeometry().clone();
+ geometry.rotate(-rotation, anchor);
+ if (geometry.intersectsExtent(extent)) {
+ selectedFeatures.push(feature);
+ }
+ });
+ } else {
+ selectedFeatures.extend(boxFeatures);
+ }
+ }
+});
+
+// clear selection when drawing a new box and when clicking on the map
+dragBox.on('boxstart', function () {
+ selectedFeatures.clear();
+});
+
+const infoBox = document.getElementById('info');
+
+selectedFeatures.on(['add', 'remove'], function () {
+ const names = selectedFeatures.getArray().map((feature) => {
+ return {
+ 'סוג מבנה': feature.get('building_type'),
+ GFID: feature.get('entity_id'),
+ רגיש: feature.get('is_sensitive')
+ };
+ });
+ console.clear();
+ if (names.length > 0) {
+ infoBox.innerHTML = JSON.stringify(names, 2, 4);
+ } else {
+ infoBox.innerHTML = 'None';
+ }
+});
+
+fetchServiceLink('raster', PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME)
+ .then(({ url, name }) =>
+ fetch(`${url}?token=${TOKEN}`)
+ .then((response) => response.text())
+ .then((text) => ({ text, name }))
+ )
+ .then(({ text, name }) => {
+ const results = WMTSParser.read(text);
+ const options = ol.source.WMTS.optionsFromCapabilities(results, {
+ layer: name
+ });
+ options.urls = options.urls.map((url) => {
+ return url.concat(`?token=${TOKEN}`);
+ });
+ const rasterLayer = new ol.layer.Tile({ opacity: 1, source: new ol.source.WMTS(options) });
+ map.addLayer(rasterLayer);
+ map.addLayer(vectorLayer);
+ });
diff --git a/examples/openlayers-box-selection/openlayers.css b/examples/openlayers-box-selection/openlayers.css
new file mode 100644
index 0000000..72b367f
--- /dev/null
+++ b/examples/openlayers-box-selection/openlayers.css
@@ -0,0 +1,6 @@
+#map {
+ top: 0;
+ bottom: 0;
+ height: 50%;
+ width: 100%;
+}
diff --git a/examples/openlayers-debug-layer/debug-layer.js b/examples/openlayers-debug-layer/debug-layer.js
new file mode 100644
index 0000000..b55af11
--- /dev/null
+++ b/examples/openlayers-debug-layer/debug-layer.js
@@ -0,0 +1,42 @@
+import { TOKEN } from './config/common-config.js';
+import { PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME } from './config/raster-config.js';
+import { fetchServiceLink } from './utils/catalog-client.js';
+
+const map = new ol.Map({
+ target: 'map',
+ view: new ol.View({
+ center: [0, 0],
+ zoom: 0,
+ projection: 'EPSG:4326'
+ })
+});
+
+const WMTSParser = new ol.format.WMTSCapabilities();
+
+fetchServiceLink('raster', PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME)
+ .then(({ url, name }) =>
+ fetch(`${url}?token=${TOKEN}`)
+ .then((response) => response.text())
+ .then((text) => ({ text, name }))
+ )
+ .then(({ text, name }) => {
+ const results = WMTSParser.read(text);
+ const options = ol.source.WMTS.optionsFromCapabilities(results, {
+ layer: name
+ });
+ options.urls = options.urls.map((url) => {
+ return url.concat(`?token=${TOKEN}`);
+ });
+ const layer = new ol.layer.WebGLTile({ opacity: 1, source: new ol.source.WMTS(options) });
+ map.addLayer(layer);
+
+ const debugLayer = new ol.layer.WebGLTile({
+ source: new ol.source.TileDebug({
+ template: 'z:{z} x:{x}, y:{y}',
+ projection: layer.getSource().getProjection(),
+ tileGrid: layer.getSource().getTileGrid(),
+ zDirection: 1
+ })
+ });
+ map.addLayer(debugLayer);
+ });
diff --git a/examples/openlayers-debug-layer/openlayers.css b/examples/openlayers-debug-layer/openlayers.css
new file mode 100644
index 0000000..3e6b562
--- /dev/null
+++ b/examples/openlayers-debug-layer/openlayers.css
@@ -0,0 +1,6 @@
+#map {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+}
diff --git a/examples/openlayers-debug-layer/openlayers.html b/examples/openlayers-debug-layer/openlayers.html
new file mode 100644
index 0000000..ad19e7d
--- /dev/null
+++ b/examples/openlayers-debug-layer/openlayers.html
@@ -0,0 +1 @@
+
diff --git a/examples/openlayers-draw-and-modify/draw-and-modify.css b/examples/openlayers-draw-and-modify/draw-and-modify.css
new file mode 100644
index 0000000..110b53a
--- /dev/null
+++ b/examples/openlayers-draw-and-modify/draw-and-modify.css
@@ -0,0 +1,6 @@
+#map {
+ height: 70%;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+}
diff --git a/examples/openlayers-draw-and-modify/draw-and-modify.html b/examples/openlayers-draw-and-modify/draw-and-modify.html
new file mode 100644
index 0000000..cae5273
--- /dev/null
+++ b/examples/openlayers-draw-and-modify/draw-and-modify.html
@@ -0,0 +1,30 @@
+
+
+
+
+
Example of using Draw and Modify interactions for geodesic circles.
+
+
+ Example of using the ol/interaction/Draw interaction with a custom geometry
+ function together with the ol/interaction/Modify interaction to draw and modify
+ geodesic circles (a ol/geom/Polygon#circular polygon representing a circle on the
+ surface of the Earth's sphere). The polygon is placed in a
+ ol/geom/GeometryCollection together with a ol/geom/Point which
+ allows the Modify interaction to adjust the circle center as well as the radius. Custom style
+ functions ensure the correct final geometry is displayed throughout.
+ ol/geom/Circle projected (planar) geometries can also be drawn and modified. The
+ difference between geodesic and projected circles can be seen when their centers are moved
+ between northern and southern latitudes in the Web Mercator projection. The
+ ol/interaction/Snap interaction can be used to create concentric circles.
+
+
+
diff --git a/examples/openlayers-draw-and-modify/draw-and-modify.js b/examples/openlayers-draw-and-modify/draw-and-modify.js
new file mode 100644
index 0000000..512d8cc
--- /dev/null
+++ b/examples/openlayers-draw-and-modify/draw-and-modify.js
@@ -0,0 +1,189 @@
+import { TOKEN } from './config/common-config.js';
+import { PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME } from './config/raster-config.js';
+import { fetchServiceLink } from './utils/catalog-client.js';
+
+const map = new ol.Map({
+ layers: [],
+ target: 'map',
+ view: new ol.View({
+ center: [0, 0],
+ zoom: 0,
+ projection: 'EPSG:4326'
+ })
+});
+
+const WMTSParser = new ol.format.WMTSCapabilities();
+
+const source = new ol.source.Vector();
+
+const style = new ol.style.Style({
+ fill: new ol.style.Fill({
+ color: 'rgba(255, 255, 255, 0.2)'
+ }),
+ stroke: new ol.style.Stroke({
+ color: '#33cc33',
+ width: 2
+ }),
+ image: new ol.style.Circle({
+ radius: 7,
+ fill: new ol.style.Fill({
+ color: '#ffcc33'
+ })
+ })
+});
+
+const geodesicStyle = new ol.style.Style({
+ geometry: function (feature) {
+ return feature.get('modifyGeometry') || feature.getGeometry();
+ },
+ fill: new ol.style.Fill({
+ color: 'rgba(255, 255, 255, 0.2)'
+ }),
+ stroke: new ol.style.Stroke({
+ color: '#ff3333',
+ width: 2
+ }),
+ image: new ol.style.Circle({
+ radius: 7,
+ fill: new ol.style.Fill({
+ color: 'rgba(0, 0, 0, 0)'
+ })
+ })
+});
+
+const vector = new ol.layer.Vector({
+ source: source,
+ style: function (feature) {
+ const geometry = feature.getGeometry();
+ return geometry.getType() === 'GeometryCollection' ? geodesicStyle : style;
+ }
+});
+
+const defaultStyle = new ol.interaction.Modify({ source: source }).getOverlay().getStyleFunction();
+
+const modify = new ol.interaction.Modify({
+ source: source,
+ style: function (feature) {
+ feature.get('features').forEach(function (modifyFeature) {
+ const modifyGeometry = modifyFeature.get('modifyGeometry');
+ if (modifyGeometry) {
+ const modifyPoint = feature.getGeometry().getCoordinates();
+ const geometries = modifyFeature.getGeometry().getGeometries();
+ const polygon = geometries[0].getCoordinates()[0];
+ const center = geometries[1].getCoordinates();
+ const projection = map.getView().getProjection();
+ let first, last, radius;
+ if (modifyPoint[0] === center[0] && modifyPoint[1] === center[1]) {
+ // center is being modified
+ // get unchanged radius from diameter between polygon vertices
+ first = ol.proj.transform(polygon[0], projection, 'EPSG:4326');
+ last = ol.proj.transform(polygon[(polygon.length - 1) / 2], projection, 'EPSG:4326');
+ radius = ol.sphere.getDistance(first, last) / 2;
+ } else {
+ // radius is being modified
+ first = ol.proj.transform(center, projection, 'EPSG:4326');
+ last = ol.proj.transform(modifyPoint, projection, 'EPSG:4326');
+ radius = ol.sphere.getDistance(first, last);
+ }
+ // update the polygon using new center or radius
+ const circle = ol.geom.Circle(
+ ol.proj.transform(center, projection, 'EPSG:4326'),
+ radius,
+ 128
+ );
+ circle.transform('EPSG:4326', projection);
+ geometries[0].setCoordinates(circle.getCoordinates());
+ // save changes to be applied at the end of the interaction
+ modifyGeometry.setGeometries(geometries);
+ }
+ });
+ return defaultStyle(feature);
+ }
+});
+
+modify.on('modifystart', function (event) {
+ event.features.forEach(function (feature) {
+ const geometry = feature.getGeometry();
+ if (geometry.getType() === 'GeometryCollection') {
+ feature.set('modifyGeometry', geometry.clone(), true);
+ }
+ });
+});
+
+modify.on('modifyend', function (event) {
+ event.features.forEach(function (feature) {
+ const modifyGeometry = feature.get('modifyGeometry');
+ if (modifyGeometry) {
+ feature.setGeometry(modifyGeometry);
+ feature.unset('modifyGeometry', true);
+ }
+ });
+});
+
+map.addInteraction(modify);
+
+let draw, snap; // global so we can remove them later
+const typeSelect = document.getElementById('type');
+
+function addInteractions() {
+ let value = typeSelect.value;
+ let geometryFunction;
+ if (value === 'Geodesic') {
+ value = 'Circle';
+ geometryFunction = function (coordinates, geometry, projection) {
+ if (!geometry) {
+ geometry = new ol.geom.GeometryCollection([
+ new ol.geom.Polygon([]),
+ new ol.geom.Point(coordinates[0])
+ ]);
+ }
+ const geometries = geometry.getGeometries();
+ const center = ol.proj.transform(coordinates[0], projection, 'EPSG:4326');
+ const last = ol.proj.transform(coordinates[1], projection, 'EPSG:4326');
+ const radius = ol.sphere.getDistance(center, last);
+ const circle = ol.geom.Polygon.circular(center, radius, 128);
+ circle.transform('EPSG:4326', projection);
+ geometries[0].setCoordinates(circle.getCoordinates());
+ geometry.setGeometries(geometries);
+ return geometry;
+ };
+ }
+ draw = new ol.interaction.Draw({
+ source: source,
+ type: value,
+ geometryFunction: geometryFunction
+ });
+ map.addInteraction(draw);
+ snap = new ol.interaction.Snap({ source: source });
+ map.addInteraction(snap);
+}
+
+/**
+ * Handle change event.
+ */
+typeSelect.onchange = function () {
+ map.removeInteraction(draw);
+ map.removeInteraction(snap);
+ addInteractions();
+};
+
+addInteractions();
+
+fetchServiceLink('raster', PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME)
+ .then(({ url, name }) =>
+ fetch(`${url}?token=${TOKEN}`)
+ .then((response) => response.text())
+ .then((text) => ({ text, name }))
+ )
+ .then(({ text, name }) => {
+ const results = WMTSParser.read(text);
+ const options = ol.source.WMTS.optionsFromCapabilities(results, {
+ layer: name
+ });
+ options.urls = options.urls.map((url) => {
+ return url.concat(`?token=${TOKEN}`);
+ });
+ const layer = new ol.layer.WebGLTile({ opacity: 1, source: new ol.source.WMTS(options) });
+ map.addLayer(layer);
+ map.addLayer(vector);
+ });
diff --git a/examples/openlayers-overview-map/overview-map.css b/examples/openlayers-overview-map/overview-map.css
new file mode 100644
index 0000000..37d4727
--- /dev/null
+++ b/examples/openlayers-overview-map/overview-map.css
@@ -0,0 +1,39 @@
+#map {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+}
+
+.map .ol-custom-overviewmap,
+.map .ol-custom-overviewmap.ol-uncollapsible {
+ bottom: auto;
+ left: auto;
+ right: 0;
+ top: 0;
+}
+
+.map .ol-custom-overviewmap:not(.ol-collapsed) {
+ border: 1px solid black;
+}
+
+.map .ol-custom-overviewmap .ol-overviewmap-map {
+ border: none;
+ width: 300px;
+}
+
+.map .ol-custom-overviewmap .ol-overviewmap-box {
+ border: 2px solid red;
+}
+
+.map .ol-custom-overviewmap:not(.ol-collapsed) button {
+ bottom: auto;
+ left: auto;
+ right: 1px;
+ top: 1px;
+}
+
+.map .ol-rotate {
+ top: 170px;
+ right: 0;
+}
diff --git a/examples/openlayers-overview-map/overview-map.html b/examples/openlayers-overview-map/overview-map.html
new file mode 100644
index 0000000..4fa1fbc
--- /dev/null
+++ b/examples/openlayers-overview-map/overview-map.html
@@ -0,0 +1 @@
+
diff --git a/examples/openlayers-overview-map/overview-map.js b/examples/openlayers-overview-map/overview-map.js
new file mode 100644
index 0000000..d6a579c
--- /dev/null
+++ b/examples/openlayers-overview-map/overview-map.js
@@ -0,0 +1,52 @@
+import { TOKEN } from './config/common-config.js';
+import { PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME } from './config/raster-config.js';
+import { fetchServiceLink } from './utils/catalog-client.js';
+
+const WMTSParser = new ol.format.WMTSCapabilities();
+
+fetchServiceLink('raster', PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME)
+ .then(({ url, name }) =>
+ fetch(`${url}?token=${TOKEN}`)
+ .then((response) => response.text())
+ .then((text) => ({ text, name }))
+ )
+ .then(({ text, name }) => {
+ const results = WMTSParser.read(text);
+ const options = ol.source.WMTS.optionsFromCapabilities(results, {
+ layer: name
+ });
+ const optionsMiniMap = ol.source.WMTS.optionsFromCapabilities(results, {
+ layer: name
+ });
+ options.urls = options.urls.map((url) => {
+ return url.concat(`?token=${TOKEN}`);
+ });
+ optionsMiniMap.urls = optionsMiniMap.urls.map((url) => {
+ return url.concat(`?token=${TOKEN}`);
+ });
+ let rasterLayer = new ol.layer.Tile({
+ opacity: 1,
+ source: new ol.source.WMTS(options),
+ preload: 10
+ });
+ let rasterLayer2 = new ol.layer.Tile({
+ opacity: 1,
+ source: new ol.source.WMTS(optionsMiniMap)
+ });
+ const overviewMapControl = new ol.control.OverviewMap({
+ layers: [rasterLayer2],
+ collapsed: false
+ });
+
+ const map = new ol.Map({
+ controls: ol.control.defaults.defaults().extend([overviewMapControl]),
+ target: 'map',
+ layers: [rasterLayer],
+ view: new ol.View({
+ center: [34.465798, 31.513991],
+ zoom: 18,
+ projection: 'EPSG:4326',
+ minZoom: 1
+ })
+ });
+ });
diff --git a/examples/openlayers-permalink/permalink.css b/examples/openlayers-permalink/permalink.css
new file mode 100644
index 0000000..3e6b562
--- /dev/null
+++ b/examples/openlayers-permalink/permalink.css
@@ -0,0 +1,6 @@
+#map {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+}
diff --git a/examples/openlayers-permalink/permalink.html b/examples/openlayers-permalink/permalink.html
new file mode 100644
index 0000000..4fa1fbc
--- /dev/null
+++ b/examples/openlayers-permalink/permalink.html
@@ -0,0 +1 @@
+
diff --git a/examples/openlayers-permalink/permalink.js b/examples/openlayers-permalink/permalink.js
new file mode 100644
index 0000000..4467d41
--- /dev/null
+++ b/examples/openlayers-permalink/permalink.js
@@ -0,0 +1,89 @@
+import { TOKEN } from './config/common-config.js';
+import { PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME } from './config/raster-config.js';
+import { fetchServiceLink } from './utils/catalog-client.js';
+
+// default zoom, center and rotation
+let zoom = 2;
+let center = [0, 0];
+let rotation = 0;
+
+if (window.location.hash !== '') {
+ // try to restore center, zoom-level and rotation from the URL
+ const hash = window.location.hash.replace('#map=', '');
+ const parts = hash.split('/');
+ if (parts.length === 4) {
+ zoom = parseFloat(parts[0]);
+ center = [parseFloat(parts[1]), parseFloat(parts[2])];
+ rotation = parseFloat(parts[3]);
+ }
+}
+
+const WMTSParser = new ol.format.WMTSCapabilities();
+
+fetchServiceLink('raster', PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME)
+ .then(({ url, name }) =>
+ fetch(`${url}?token=${TOKEN}`)
+ .then((response) => response.text())
+ .then((text) => ({ text, name }))
+ )
+ .then(({ text, name }) => {
+ const results = WMTSParser.read(text);
+ const options = ol.source.WMTS.optionsFromCapabilities(results, {
+ layer: name
+ });
+ options.urls = options.urls.map((url) => {
+ return url.concat(`?token=${TOKEN}`);
+ });
+ const layer = new ol.layer.Tile({ opacity: 1, source: new ol.source.WMTS(options) });
+ const map = new ol.Map({
+ target: 'map',
+ view: new ol.View({
+ center: center,
+ zoom: zoom,
+ rotation: rotation,
+ projection: 'EPSG:4326'
+ })
+ });
+ map.addLayer(layer);
+
+ let shouldUpdate = true;
+ const view = map.getView();
+ const updatePermalink = function () {
+ if (!shouldUpdate) {
+ // do not update the URL when the view was changed in the 'popstate' handler
+ shouldUpdate = true;
+ return;
+ }
+
+ const center = view.getCenter();
+ const hash =
+ '#map=' +
+ view.getZoom().toFixed(2) +
+ '/' +
+ center[0].toFixed(2) +
+ '/' +
+ center[1].toFixed(2) +
+ '/' +
+ view.getRotation();
+ const state = {
+ zoom: view.getZoom(),
+ center: view.getCenter(),
+ rotation: view.getRotation()
+ };
+ window.history.pushState(state, 'map', hash);
+ };
+
+ map.on('moveend', updatePermalink);
+
+ // restore the view state when navigating through the history, see
+ // https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate
+ window.addEventListener('popstate', function (event) {
+ if (event.state === null) {
+ return;
+ }
+ map.getView().setCenter(event.state.center);
+ map.getView().setZoom(event.state.zoom);
+ map.getView().setRotation(event.state.rotation);
+ shouldUpdate = false;
+ });
+ });
diff --git a/examples/openlayers-preload/preload.css b/examples/openlayers-preload/preload.css
new file mode 100644
index 0000000..acb63b6
--- /dev/null
+++ b/examples/openlayers-preload/preload.css
@@ -0,0 +1,6 @@
+.map {
+ height: 50%;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+}
diff --git a/examples/openlayers-preload/preload.html b/examples/openlayers-preload/preload.html
new file mode 100644
index 0000000..4aa8e68
--- /dev/null
+++ b/examples/openlayers-preload/preload.html
@@ -0,0 +1,2 @@
+
+
diff --git a/examples/openlayers-preload/preload.js b/examples/openlayers-preload/preload.js
new file mode 100644
index 0000000..2aec33a
--- /dev/null
+++ b/examples/openlayers-preload/preload.js
@@ -0,0 +1,44 @@
+import { TOKEN } from './config/common-config.js';
+import { PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME } from './config/raster-config.js';
+import { fetchServiceLink } from './utils/catalog-client.js';
+
+const WMTSParser = new ol.format.WMTSCapabilities();
+
+const sharedView = new ol.View({
+ center: [0, 0],
+ zoom: 0,
+ projection: 'EPSG:4326'
+});
+
+fetchServiceLink('raster', PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME)
+ .then(({ url, name }) =>
+ fetch(`${url}?token=${TOKEN}`)
+ .then((response) => response.text())
+ .then((text) => ({ text, name }))
+ )
+ .then(({ text, name }) => {
+ const results = WMTSParser.read(text);
+ const options = ol.source.WMTS.optionsFromCapabilities(results, {
+ layer: name
+ });
+ options.urls = options.urls.map((url) => {
+ return url.concat(`?token=${TOKEN}`);
+ });
+ const layerNoPreload = new ol.layer.Tile({ opacity: 1, source: new ol.source.WMTS(options) });
+ const layerPreload = new ol.layer.Tile({
+ opacity: 1,
+ source: new ol.source.WMTS(options),
+ preload: 10
+ });
+
+ const mapPreload = new ol.Map({
+ target: 'map-preload',
+ layers: [layerPreload],
+ view: sharedView
+ });
+ const mapNoPreload = new ol.Map({
+ target: 'map-no-preload',
+ layers: [layerNoPreload],
+ view: sharedView
+ });
+ });
diff --git a/examples/openlayers-query-service/openlayers.css b/examples/openlayers-query-service/openlayers.css
new file mode 100644
index 0000000..3e6b562
--- /dev/null
+++ b/examples/openlayers-query-service/openlayers.css
@@ -0,0 +1,6 @@
+#map {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+}
diff --git a/examples/openlayers-query-service/openlayers.html b/examples/openlayers-query-service/openlayers.html
new file mode 100644
index 0000000..ad19e7d
--- /dev/null
+++ b/examples/openlayers-query-service/openlayers.html
@@ -0,0 +1 @@
+
diff --git a/examples/openlayers-query-service/query-service.js b/examples/openlayers-query-service/query-service.js
new file mode 100644
index 0000000..68d603a
--- /dev/null
+++ b/examples/openlayers-query-service/query-service.js
@@ -0,0 +1,61 @@
+import { TOKEN } from './config/common-config.js';
+import { PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME } from './config/raster-config.js';
+import { VECTOR_WFS_URL } from './config/vector-config.js';
+import { fetchServiceLink } from './utils/catalog-client.js';
+
+const map = new ol.Map({
+ target: 'map',
+ view: new ol.View({
+ center: [34.465798, 31.513991],
+ zoom: 18,
+ projection: 'EPSG:4326'
+ })
+});
+
+const WMTSParser = new ol.format.WMTSCapabilities();
+let rasterLayer;
+
+const vectorSource = new ol.source.Vector({
+ format: new ol.format.GeoJSON(),
+ strategy: ol.loadingstrategy.tile(ol.tilegrid.createXYZ({ tileSize: 512 })),
+ url: function (extent) {
+ return (
+ VECTOR_WFS_URL +
+ `?service=WFS&version=2.0.0&request=GetFeature&typeName=core:buildings_polygon&srsname=EPSG:4326&bbox=${extent.join(
+ ','
+ )},EPSG:4326&token=${TOKEN}&outputFormat=application/json&maxFeatures=10000`
+ );
+ }
+});
+
+const vector = new ol.layer.Vector({
+ source: vectorSource,
+ style: new ol.style.Style({
+ stroke: new ol.style.Stroke({
+ color: 'rgba(255,0,0,0.5)',
+ width: 3
+ }),
+ fill: new ol.style.Fill({ color: 'rgba(255,50,0,0.5)' })
+ }),
+ minZoom: 15,
+ maxZoom: 20
+});
+
+fetchServiceLink('raster', PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME)
+ .then(({ url, name }) =>
+ fetch(`${url}?token=${TOKEN}`)
+ .then((response) => response.text())
+ .then((text) => ({ text, name }))
+ )
+ .then(({ text, name }) => {
+ const results = WMTSParser.read(text);
+ const options = ol.source.WMTS.optionsFromCapabilities(results, {
+ layer: name
+ });
+ options.urls = options.urls.map((url) => {
+ return url.concat(`?token=${TOKEN}`);
+ });
+ rasterLayer = new ol.layer.Tile({ opacity: 1, source: new ol.source.WMTS(options) });
+ map.addLayer(rasterLayer);
+ map.addLayer(vector);
+ });
diff --git a/examples/openlayers-sensitive/interactive-sensitive.js b/examples/openlayers-sensitive/interactive-sensitive.js
new file mode 100644
index 0000000..8e7a86e
--- /dev/null
+++ b/examples/openlayers-sensitive/interactive-sensitive.js
@@ -0,0 +1,106 @@
+import { TOKEN } from './config/common-config.js';
+import { PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME } from './config/raster-config.js';
+import { VECTOR_WFS_URL } from './config/vector-config.js';
+import { fetchServiceLink } from './utils/catalog-client.js';
+
+const WMTSParser = new ol.format.WMTSCapabilities();
+let rasterLayer;
+
+const vectorSource = new ol.source.Vector({
+ format: new ol.format.GeoJSON(),
+ strategy: ol.loadingstrategy.tile(ol.tilegrid.createXYZ({ tileSize: 256 })),
+ url: function (extent) {
+ return (
+ VECTOR_WFS_URL +
+ `?service=WFS&version=2.0.0&request=GetFeature&typeName=core:buildings_polygon&srsname=EPSG:4326&bbox=${extent.join(
+ ','
+ )},EPSG:4326&token=${TOKEN}&outputFormat=application/json`
+ );
+ }
+});
+
+const style = new ol.style.Style({
+ fill: new ol.style.Fill({
+ color: '#eeeeee'
+ })
+});
+
+const map = new ol.Map({
+ target: 'map',
+ view: new ol.View({
+ center: [34.465798, 31.513991],
+ zoom: 18,
+ projection: 'EPSG:4326',
+ constrainRotation: 16
+ })
+});
+
+const vectorLayer = new ol.layer.Vector({
+ source: vectorSource,
+ style: function (feature) {
+ const color =
+ feature.get('is_sensitive') === true ? 'rgba(255, 0, 0, 0.3)' : 'rgba(255, 255, 255, 0.7)';
+ style.getFill().setColor(color);
+ return style;
+ }
+});
+
+const selectedStyle = new ol.style.Style({
+ fill: new ol.style.Fill({
+ color: 'rgba(180, 2, 180, 0.3)'
+ }),
+ stroke: new ol.style.Stroke({
+ color: 'rgba(180, 2, 180, 0.4)',
+ width: 10
+ })
+});
+
+// a normal select interaction to handle click
+const select = new ol.interaction.Select({
+ style: function (feature) {
+ const color =
+ feature.get('is_sensitive') === true ? 'rgba(255, 0, 0, 1)' : 'rgba(255, 255, 255, 1)';
+ selectedStyle.getFill().setColor(color);
+ return selectedStyle;
+ }
+});
+map.addInteraction(select);
+
+const selectedFeatures = select.getFeatures();
+
+const infoBox = document.getElementById('info');
+
+selectedFeatures.on(['add', 'remove'], function () {
+ const names = selectedFeatures.getArray().map((feature) => {
+ return {
+ 'סוג מבנה': feature.get('building_type'),
+ GFID: feature.get('entity_id'),
+ רגיש: feature.get('is_sensitive')
+ };
+ });
+ console.clear();
+ if (names.length > 0) {
+ infoBox.innerHTML = JSON.stringify(names, 2, 4);
+ } else {
+ infoBox.innerHTML = 'None';
+ }
+});
+
+fetchServiceLink('raster', PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME)
+ .then(({ url, name }) =>
+ fetch(`${url}?token=${TOKEN}`)
+ .then((response) => response.text())
+ .then((text) => ({ text, name }))
+ )
+ .then(({ text, name }) => {
+ const results = WMTSParser.read(text);
+ const options = ol.source.WMTS.optionsFromCapabilities(results, {
+ layer: name
+ });
+ options.urls = options.urls.map((url) => {
+ return url.concat(`?token=${TOKEN}`);
+ });
+ rasterLayer = new ol.layer.Tile({ opacity: 1, source: new ol.source.WMTS(options) });
+ map.addLayer(rasterLayer);
+ map.addLayer(vectorLayer);
+ });
diff --git a/examples/openlayers-sensitive/openlayers.css b/examples/openlayers-sensitive/openlayers.css
new file mode 100644
index 0000000..72b367f
--- /dev/null
+++ b/examples/openlayers-sensitive/openlayers.css
@@ -0,0 +1,6 @@
+#map {
+ top: 0;
+ bottom: 0;
+ height: 50%;
+ width: 100%;
+}
diff --git a/examples/openlayers-sensitive/openlayers.html b/examples/openlayers-sensitive/openlayers.html
new file mode 100644
index 0000000..e55b80c
--- /dev/null
+++ b/examples/openlayers-sensitive/openlayers.html
@@ -0,0 +1,7 @@
+
+
+
+ Click on a sensitive feature (colored RED) to see details:
+
+
+
diff --git a/examples/openlayers-street-labels/street-labels.css b/examples/openlayers-street-labels/street-labels.css
new file mode 100644
index 0000000..3e6b562
--- /dev/null
+++ b/examples/openlayers-street-labels/street-labels.css
@@ -0,0 +1,6 @@
+#map {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+}
diff --git a/examples/openlayers-street-labels/street-labels.html b/examples/openlayers-street-labels/street-labels.html
new file mode 100644
index 0000000..ad19e7d
--- /dev/null
+++ b/examples/openlayers-street-labels/street-labels.html
@@ -0,0 +1 @@
+
diff --git a/examples/openlayers-street-labels/street-labels.js b/examples/openlayers-street-labels/street-labels.js
new file mode 100644
index 0000000..f408044
--- /dev/null
+++ b/examples/openlayers-street-labels/street-labels.js
@@ -0,0 +1,76 @@
+import { TOKEN } from './config/common-config.js';
+import { PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME } from './config/raster-config.js';
+import { fetchServiceLink } from './utils/catalog-client.js';
+
+const WMTSParser = new ol.format.WMTSCapabilities();
+let rasterLayer;
+
+const style = new ol.style.Style({
+ text: new ol.style.Text({
+ font: '20px "sans-serif"',
+ placement: 'line',
+ rotationWithView: true,
+ stroke: new ol.style.Stroke({
+ color: 'white'
+ }),
+ fill: new ol.style.Fill({
+ color: 'red'
+ })
+ }),
+ stroke: new ol.style.Stroke({
+ color: 'red'
+ })
+});
+
+const vectorSource = new ol.source.Vector({
+ format: new ol.format.GeoJSON(),
+ strategy: ol.loadingstrategy.tile(ol.tilegrid.createXYZ({ tileSize: 256 })),
+ url: function (extent) {
+ return (
+ VECTOR_WFS_URL +
+ `?service=WFS&version=2.0.0&request=GetFeature&typeName=core:roads_line&srsname=EPSG:4326&bbox=${extent.join(
+ ','
+ )},EPSG:4326&token=${TOKEN}&outputFormat=application/json&count=10000`
+ );
+ }
+});
+
+const vectorLayer = new ol.layer.Vector({
+ declutter: true,
+ source: vectorSource,
+ style: function (feature) {
+ style.getText().setText(feature.get('name') === null ? 'לא ידוע' : feature.get('name'));
+ return style;
+ },
+ minZoom: 15,
+ maxZoom: 20
+});
+
+const map = new ol.Map({
+ target: 'map',
+ view: new ol.View({
+ center: [34.465798, 31.513991],
+ zoom: 18,
+ projection: 'EPSG:4326',
+ minZoom: 12
+ })
+});
+
+fetchServiceLink('raster', PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME)
+ .then(({ url, name }) =>
+ fetch(`${url}?token=${TOKEN}`)
+ .then((response) => response.text())
+ .then((text) => ({ text, name }))
+ )
+ .then(({ text, name }) => {
+ const results = WMTSParser.read(text);
+ const options = ol.source.WMTS.optionsFromCapabilities(results, {
+ layer: name
+ });
+ options.urls = options.urls.map((url) => {
+ return url.concat(`?token=${TOKEN}`);
+ });
+ rasterLayer = new ol.layer.Tile({ opacity: 1, source: new ol.source.WMTS(options) });
+ map.addLayer(rasterLayer);
+ map.addLayer(vectorLayer);
+ });
diff --git a/examples/openlayers-wmts/openlayers.css b/examples/openlayers-wmts/openlayers.css
new file mode 100644
index 0000000..3e6b562
--- /dev/null
+++ b/examples/openlayers-wmts/openlayers.css
@@ -0,0 +1,6 @@
+#map {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+}
diff --git a/examples/openlayers-wmts/openlayers.html b/examples/openlayers-wmts/openlayers.html
new file mode 100644
index 0000000..ad19e7d
--- /dev/null
+++ b/examples/openlayers-wmts/openlayers.html
@@ -0,0 +1 @@
+
diff --git a/examples/openlayers-wmts/wmts.js b/examples/openlayers-wmts/wmts.js
new file mode 100644
index 0000000..7214905
--- /dev/null
+++ b/examples/openlayers-wmts/wmts.js
@@ -0,0 +1,32 @@
+import { TOKEN } from './config/common-config.js';
+import { PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME } from './config/raster-config.js';
+import { fetchServiceLink } from './utils/catalog-client.js';
+
+const WMTSParser = new ol.format.WMTSCapabilities();
+
+const map = new ol.Map({
+ target: 'map',
+ view: new ol.View({
+ center: [0, 0],
+ zoom: 0,
+ projection: 'EPSG:4326'
+ })
+});
+
+fetchServiceLink('raster', PRODUCT_ID, PRODUCT_TYPE, RASTER_SCHEME)
+ .then(({ url, name }) =>
+ fetch(`${url}?token=${TOKEN}`)
+ .then((response) => response.text())
+ .then((text) => ({ text, name }))
+ )
+ .then(({ text, name }) => {
+ const results = WMTSParser.read(text);
+ const options = ol.source.WMTS.optionsFromCapabilities(results, {
+ layer: name
+ });
+ options.urls = options.urls.map((url) => {
+ return url.concat(`?token=${TOKEN}`);
+ });
+ const layer = new ol.layer.Tile({ opacity: 1, source: new ol.source.WMTS(options) });
+ map.addLayer(layer);
+ });
diff --git a/examples/utils/catalog-client.js b/examples/utils/catalog-client.js
new file mode 100644
index 0000000..34c3c8e
--- /dev/null
+++ b/examples/utils/catalog-client.js
@@ -0,0 +1,91 @@
+import { MAPCOLONIES_CATALOG_URL, TOKEN } from './config/common-config.js';
+import { parseXml } from './utils/xml-utils.js';
+
+const TYPENAMES = {
+ raster: 'mc:MCRasterRecord',
+ '3d': 'mc:MC3DRecord',
+ dem: 'mc:MCDEMRecord'
+};
+
+const namespaceFor = (catalogKey) => `http://schema.mapcolonies.com/${catalogKey}`;
+
+/**
+ * Builds a CSW 2.0.2 GetRecords XML body that filters by productId and productType.
+ *
+ * @param {string} typename - CSW typeName for the catalog (e.g. 'mc:MCRasterRecord').
+ * @param {string} namespace - XML namespace URI for the catalog schema.
+ * @param {string} productId - Product identifier to match (e.g. 'blueMarble').
+ * @param {string} productType - Product type to match (e.g. 'Orthophoto').
+ * @returns {string} CSW GetRecords request XML.
+ */
+function buildGetRecordsBody(typename, namespace, productId, productType) {
+ return `
+
+
+ full
+
+
+
+
+ mc:productId
+ ${productId}
+
+
+ mc:productType
+ ${productType}
+
+
+
+
+
+`;
+}
+
+/**
+ * Parses a CSW response and returns the service URL and layer name for the requested scheme.
+ *
+ * @param {string} xmlText - Raw CSW response XML.
+ * @param {string} namespace - Namespace URI used for `` elements in the response.
+ * @param {string} scheme - Scheme name to pick (e.g. 'WMTS', 'WCS', '3DTiles').
+ * @returns {{ url: string, name: string } | null} Service URL and layer name from the `name` attribute, or null if no matching `` element is present.
+ */
+function parseLink(xmlText, namespace, scheme) {
+ const doc = parseXml(xmlText, 'CSW response');
+ const node = Array.from(doc.getElementsByTagNameNS(namespace, 'links')).find(
+ (n) => n.getAttribute('scheme') === scheme
+ );
+ if (!node) return null;
+ return {
+ url: (node.textContent || '').trim(),
+ name: node.getAttribute('name') || ''
+ };
+}
+
+/**
+ * Resolves the service URL and layer name for a specific scheme on a catalog record.
+ *
+ * @param {'raster'|'3d'|'dem'} catalogKey - Catalog the product lives in.
+ * @param {string} productId - Product identifier.
+ * @param {string} productType - Product type.
+ * @param {string} scheme - Scheme name to pick from the record (e.g. 'WMTS', 'WCS', '3DTiles').
+ * @returns {Promise<{ url: string, name: string }>} Service URL and layer name from the matched `` element.
+ * @throws If the catalog key is unknown, the CSW request fails, or the scheme is not advertised.
+ */
+export async function fetchServiceLink(catalogKey, productId, productType, scheme) {
+ const typename = TYPENAMES[catalogKey];
+ if (!typename) throw new Error(`Unknown catalog: ${catalogKey}`);
+ const namespace = namespaceFor(catalogKey);
+ const res = await fetch(`${MAPCOLONIES_CATALOG_URL}/api/${catalogKey}/v1/csw`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/xml', 'x-api-key': TOKEN },
+ body: buildGetRecordsBody(typename, namespace, productId, productType)
+ });
+ if (!res.ok) {
+ throw new Error(`CSW ${catalogKey} ${productId} failed: ${res.status}`);
+ }
+ const link = parseLink(await res.text(), namespace, scheme);
+ if (!link) {
+ throw new Error(`No "${scheme}" link in ${catalogKey}/${productId}`);
+ }
+ return link;
+}
diff --git a/examples/utils/wmts-utils.js b/examples/utils/wmts-utils.js
new file mode 100644
index 0000000..fa4985e
--- /dev/null
+++ b/examples/utils/wmts-utils.js
@@ -0,0 +1,69 @@
+import { TOKEN } from './config/common-config.js';
+import { RASTER_SCHEME } from './config/raster-config.js';
+import { fetchServiceLink } from './utils/catalog-client.js';
+import { parseXml } from './utils/xml-utils.js';
+
+const WMTS_NS = 'http://www.opengis.net/wmts/1.0';
+const OWS_NS = 'http://www.opengis.net/ows/1.1';
+
+/**
+ * Extracts the RESTful tile URL template for a specific layer from a WMTS capabilities document.
+ *
+ * The returned template contains WMTS placeholders ({TileMatrix}, {TileRow}, {TileCol}, ...)
+ * that the caller is expected to substitute when requesting tiles.
+ *
+ * @param {string} capabilitiesXml - Raw WMTS capabilities XML.
+ * @param {string} layerName - `` of the layer to look up.
+ * @param {string} [format] - Optional MIME type to require on the ResourceURL (e.g. 'image/png').
+ * @returns {string} The tile URL template.
+ * @throws If the layer is not present, or no matching tile ResourceURL is found.
+ */
+export function extractWmtsTileTemplate(capabilitiesXml, layerName, format) {
+ const doc = parseXml(capabilitiesXml, 'WMTS capabilities');
+ // Locate the whose matches the requested layerName.
+ const layer = Array.from(doc.getElementsByTagNameNS(WMTS_NS, 'Layer')).find((node) => {
+ const identifier = node.getElementsByTagNameNS(OWS_NS, 'Identifier')[0];
+ return identifier && identifier.textContent.trim() === layerName;
+ });
+ if (!layer) {
+ throw new Error(`Layer "${layerName}" not found in WMTS capabilities`);
+ }
+ // Pick the tile ResourceURL, optionally constrained to the requested format.
+ const resourceUrl = Array.from(layer.getElementsByTagNameNS(WMTS_NS, 'ResourceURL')).find(
+ (node) =>
+ node.getAttribute('resourceType') === 'tile' &&
+ (!format || node.getAttribute('format') === format)
+ );
+ if (!resourceUrl) {
+ throw new Error(`No tile ResourceURL for layer "${layerName}"`);
+ }
+ return resourceUrl.getAttribute('template');
+}
+
+/**
+ * Resolves the raster catalog entry for a product, fetches its WMTS capabilities,
+ * and returns the tile URL template along with the layer name from the catalog.
+ *
+ * Convenience helper that wraps the full catalog → capabilities → template chain.
+ *
+ * @param {string} productId - Raster product identifier.
+ * @param {string} productType - Raster product type.
+ * @param {string} [format] - Optional MIME type filter passed to `extractWmtsTileTemplate`.
+ * @returns {Promise<{ template: string, name: string }>} Tile URL template (still contains WMTS placeholders) and the layer name reported by the catalog.
+ * @throws If the catalog lookup, capabilities fetch, or layer extraction fails.
+ */
+export async function fetchWmtsTileTemplate(productId, productType, format) {
+ const { url: capabilitiesUrl, name } = await fetchServiceLink(
+ 'raster',
+ productId,
+ productType,
+ RASTER_SCHEME
+ );
+ // Capabilities endpoint is token-gated; same token is later used per-tile by the caller.
+ const res = await fetch(`${capabilitiesUrl}?token=${TOKEN}`);
+ if (!res.ok) {
+ throw new Error(`Fetching WMTS capabilities failed: ${res.status}`);
+ }
+ const template = extractWmtsTileTemplate(await res.text(), name, format);
+ return { template, name };
+}
diff --git a/examples/utils/xml-utils.js b/examples/utils/xml-utils.js
new file mode 100644
index 0000000..159ab6c
--- /dev/null
+++ b/examples/utils/xml-utils.js
@@ -0,0 +1,8 @@
+export function parseXml(xmlText, context) {
+ const doc = new DOMParser().parseFromString(xmlText, 'application/xml');
+ const parseError = doc.getElementsByTagName('parsererror')[0];
+ if (parseError) {
+ throw new Error(`${context} parse error: ${parseError.textContent}`);
+ }
+ return doc;
+}
diff --git a/package-lock.json b/package-lock.json
index d98b531..27d5e95 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -41,7 +41,8 @@
"tslib": "^2.4.1",
"typescript": "~5.4.0",
"vite": "^4.3.0",
- "vite-plugin-static-copy": "^0.15.0"
+ "vite-plugin-static-copy": "^0.15.0",
+ "zx": "^8.8.5"
}
},
"node_modules/@algolia/abtesting": {
@@ -404,16 +405,16 @@
}
},
"node_modules/@aws-sdk/checksums": {
- "version": "3.1000.1",
- "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.1.tgz",
- "integrity": "sha512-DFCtlisEuWzw7rESV65jHK7De1QsJZRZgUNJ8ovpmdVaayPrxvmlsAlW8hka9E7f9B31d1T7lHG9oozZf6Bp6w==",
+ "version": "3.1000.2",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.2.tgz",
+ "integrity": "sha512-PIha+kauTbp6IRmOpYktPTrlfrrSqDVixvhO/EUOFOf62DPX81CaJoHJreuA1m9HYpSKyXf99BKjU1dvJPeUfw==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/crc32": "5.2.0",
"@aws-crypto/crc32c": "5.2.0",
"@aws-crypto/util": "5.2.0",
- "@aws-sdk/core": "^3.974.17",
- "@aws-sdk/types": "^3.973.10",
+ "@aws-sdk/core": "^3.974.18",
+ "@aws-sdk/types": "^3.973.11",
"@smithy/core": "^3.24.6",
"@smithy/types": "^4.14.3",
"tslib": "^2.6.2"
@@ -423,24 +424,20 @@
}
},
"node_modules/@aws-sdk/client-s3": {
- "version": "3.1061.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1061.0.tgz",
- "integrity": "sha512-ygyRCIkktaDz4/kNzsxhbZqocLwCJV5absi/k7Xd3LThPOmVkid7Nghm/xTW2Yg+vSQIL0yq99oV7u3T+4ZbAQ==",
+ "version": "3.1063.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1063.0.tgz",
+ "integrity": "sha512-ETn+vvmZVK1MmOZwVBXmWANpmD5iTbzojIqyEIoZ86qo+8oWy35S8QyQNE/ZDI+WHgMU1dS+VSYbpRl1QkEySg==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha1-browser": "5.2.0",
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "^3.974.17",
- "@aws-sdk/credential-provider-node": "^3.972.50",
- "@aws-sdk/middleware-bucket-endpoint": "^3.972.20",
- "@aws-sdk/middleware-expect-continue": "^3.972.16",
- "@aws-sdk/middleware-flexible-checksums": "^3.974.26",
- "@aws-sdk/middleware-location-constraint": "^3.972.13",
- "@aws-sdk/middleware-sdk-s3": "^3.972.47",
- "@aws-sdk/middleware-ssec": "^3.972.13",
- "@aws-sdk/signature-v4-multi-region": "^3.996.31",
- "@aws-sdk/types": "^3.973.10",
+ "@aws-sdk/core": "^3.974.18",
+ "@aws-sdk/credential-provider-node": "^3.972.52",
+ "@aws-sdk/middleware-flexible-checksums": "^3.974.27",
+ "@aws-sdk/middleware-sdk-s3": "^3.972.48",
+ "@aws-sdk/signature-v4-multi-region": "^3.996.32",
+ "@aws-sdk/types": "^3.973.11",
"@smithy/core": "^3.24.6",
"@smithy/fetch-http-handler": "^5.4.6",
"@smithy/node-http-handler": "^4.7.6",
@@ -452,13 +449,13 @@
}
},
"node_modules/@aws-sdk/core": {
- "version": "3.974.17",
- "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.17.tgz",
- "integrity": "sha512-r8o4h2K7j6P9ngno+8ei0aK0U/4JwDb7A2fMMxGVoSqDN8AFlIzSDeZHME9LcVLR2codyhtr1WAAg+/nmkeeMA==",
+ "version": "3.974.18",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.18.tgz",
+ "integrity": "sha512-JDYCPI0j7zGrzXTDFsLB346cxss7J/AxH7+O0MzWlqppJBEyB9Qe6TQXRL6iwLUo/xZkNv9KFmBL2hqElmwW0g==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "^3.973.10",
- "@aws-sdk/xml-builder": "^3.972.27",
+ "@aws-sdk/types": "^3.973.11",
+ "@aws-sdk/xml-builder": "^3.972.28",
"@aws/lambda-invoke-store": "^0.2.2",
"@smithy/core": "^3.24.6",
"@smithy/signature-v4": "^5.4.6",
@@ -471,13 +468,13 @@
}
},
"node_modules/@aws-sdk/credential-provider-env": {
- "version": "3.972.43",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.43.tgz",
- "integrity": "sha512-g0XVQKzaA/4cq1vz1IvCQwYM+1Pkv01J9yHDpCTXekVuGZRDEz0wqBQ1AuYTq7FM6uik4uBGH8Tb5d9YvgeA7g==",
+ "version": "3.972.44",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.44.tgz",
+ "integrity": "sha512-3hKJVrZ7bqXzDAXCQp+OaQ1ASN+vWstaNuEH418wQVl//cRZhqhfR9Bjk1qIWmgUGe8/D3gdO73PgidRj378EQ==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.974.17",
- "@aws-sdk/types": "^3.973.10",
+ "@aws-sdk/core": "^3.974.18",
+ "@aws-sdk/types": "^3.973.11",
"@smithy/core": "^3.24.6",
"@smithy/types": "^4.14.3",
"tslib": "^2.6.2"
@@ -487,13 +484,13 @@
}
},
"node_modules/@aws-sdk/credential-provider-http": {
- "version": "3.972.45",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.45.tgz",
- "integrity": "sha512-w9PuOoKCt6+xoESvY+zlV0u3PKQ0mVL259PcsVR6a3S/uYJJHnIi4r1NxdJHEcNldUVRIciltWnFMGBR4YEm3g==",
+ "version": "3.972.46",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.46.tgz",
+ "integrity": "sha512-VhwC9pGAZHhiQ2xSViyOPDFqvr9aRxGCAXZtADsUhU3R65nad7y//CwynE6mQnWNR+suRlqE79W36IVayL+m1g==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.974.17",
- "@aws-sdk/types": "^3.973.10",
+ "@aws-sdk/core": "^3.974.18",
+ "@aws-sdk/types": "^3.973.11",
"@smithy/core": "^3.24.6",
"@smithy/fetch-http-handler": "^5.4.6",
"@smithy/node-http-handler": "^4.7.6",
@@ -505,20 +502,20 @@
}
},
"node_modules/@aws-sdk/credential-provider-ini": {
- "version": "3.972.48",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.48.tgz",
- "integrity": "sha512-+6BQ6Lrnc+EyAGElLRW6j+Sa+RirPHnIJsobvYO6nnyK+oGKmz1ne/ieclbLWyjyDKEU3/JVJWcWY3VLFPvGtQ==",
+ "version": "3.972.50",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.50.tgz",
+ "integrity": "sha512-09Xi6ovxiK42+De/qBGF71sT5F2bWgYM+1fFyDwSOpy1xpsQ5R/naIu7MVDpH6Dic36QNc8dAv4KADtMGK2JYg==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.974.17",
- "@aws-sdk/credential-provider-env": "^3.972.43",
- "@aws-sdk/credential-provider-http": "^3.972.45",
- "@aws-sdk/credential-provider-login": "^3.972.47",
- "@aws-sdk/credential-provider-process": "^3.972.43",
- "@aws-sdk/credential-provider-sso": "^3.972.47",
- "@aws-sdk/credential-provider-web-identity": "^3.972.47",
- "@aws-sdk/nested-clients": "^3.997.15",
- "@aws-sdk/types": "^3.973.10",
+ "@aws-sdk/core": "^3.974.18",
+ "@aws-sdk/credential-provider-env": "^3.972.44",
+ "@aws-sdk/credential-provider-http": "^3.972.46",
+ "@aws-sdk/credential-provider-login": "^3.972.49",
+ "@aws-sdk/credential-provider-process": "^3.972.44",
+ "@aws-sdk/credential-provider-sso": "^3.972.49",
+ "@aws-sdk/credential-provider-web-identity": "^3.972.49",
+ "@aws-sdk/nested-clients": "^3.997.17",
+ "@aws-sdk/types": "^3.973.11",
"@smithy/core": "^3.24.6",
"@smithy/credential-provider-imds": "^4.3.7",
"@smithy/types": "^4.14.3",
@@ -529,14 +526,14 @@
}
},
"node_modules/@aws-sdk/credential-provider-login": {
- "version": "3.972.47",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.47.tgz",
- "integrity": "sha512-Iy2ebWVgrZBH05464uJiQYu6HSSiROnwVZptthEFXx2gWjo1ORCxEAFZB5Cr2MdfrSnZ+0QUPkZ1ZpCqpkUrLQ==",
+ "version": "3.972.49",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.49.tgz",
+ "integrity": "sha512-EfJF/1Fh9mI4pZyoheU2RY9xUhTcugIZNkD63+orXMkYj/QXacJNbKVDUK90Yv5hE+aX+rt9J/EZ9Qr3vKOa7g==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.974.17",
- "@aws-sdk/nested-clients": "^3.997.15",
- "@aws-sdk/types": "^3.973.10",
+ "@aws-sdk/core": "^3.974.18",
+ "@aws-sdk/nested-clients": "^3.997.17",
+ "@aws-sdk/types": "^3.973.11",
"@smithy/core": "^3.24.6",
"@smithy/types": "^4.14.3",
"tslib": "^2.6.2"
@@ -546,18 +543,18 @@
}
},
"node_modules/@aws-sdk/credential-provider-node": {
- "version": "3.972.50",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.50.tgz",
- "integrity": "sha512-b05Aelq5cqAvCCDQjCYacl0XmR8QhBNSqLbsdISkQmlQBa5oPS66zYPteWcSp5LswbpoIe552EUGjluKiadBig==",
+ "version": "3.972.52",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.52.tgz",
+ "integrity": "sha512-7QX+PbyiWBEOVipJq8Nke/TqXT6lAPLE7fvTaopa39/IVWuLfS+Fzdy71sZJONf/mLGgmtj6aU17+REw3+aRrw==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/credential-provider-env": "^3.972.43",
- "@aws-sdk/credential-provider-http": "^3.972.45",
- "@aws-sdk/credential-provider-ini": "^3.972.48",
- "@aws-sdk/credential-provider-process": "^3.972.43",
- "@aws-sdk/credential-provider-sso": "^3.972.47",
- "@aws-sdk/credential-provider-web-identity": "^3.972.47",
- "@aws-sdk/types": "^3.973.10",
+ "@aws-sdk/credential-provider-env": "^3.972.44",
+ "@aws-sdk/credential-provider-http": "^3.972.46",
+ "@aws-sdk/credential-provider-ini": "^3.972.50",
+ "@aws-sdk/credential-provider-process": "^3.972.44",
+ "@aws-sdk/credential-provider-sso": "^3.972.49",
+ "@aws-sdk/credential-provider-web-identity": "^3.972.49",
+ "@aws-sdk/types": "^3.973.11",
"@smithy/core": "^3.24.6",
"@smithy/credential-provider-imds": "^4.3.7",
"@smithy/types": "^4.14.3",
@@ -568,13 +565,13 @@
}
},
"node_modules/@aws-sdk/credential-provider-process": {
- "version": "3.972.43",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.43.tgz",
- "integrity": "sha512-GPokLNyvTfCmuaHk+v3GKVs4ZT3cMu5kgS2a+NPkOMt96cq6fSIK0g+mZHpGS6Cd4QGrPKesANEaLUKgOskTzg==",
+ "version": "3.972.44",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.44.tgz",
+ "integrity": "sha512-V+UUhZpRP7QDRhi+qgBDisM9tUBnYmMje8Bk77A6MZsfeGeGdMsQXmaHP1CDYFcept0o/Rz5g2Y0TMeVlG9dzg==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.974.17",
- "@aws-sdk/types": "^3.973.10",
+ "@aws-sdk/core": "^3.974.18",
+ "@aws-sdk/types": "^3.973.11",
"@smithy/core": "^3.24.6",
"@smithy/types": "^4.14.3",
"tslib": "^2.6.2"
@@ -584,15 +581,15 @@
}
},
"node_modules/@aws-sdk/credential-provider-sso": {
- "version": "3.972.47",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.47.tgz",
- "integrity": "sha512-0AzvLrzlvJs0DzbeWGvNj+bX3Uzd7VNS6vDqCOdZzBlCGKGd78uxctJSW9iK/Rt/nxiJqpTvrYQlVJ4guVM2Dw==",
+ "version": "3.972.49",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.49.tgz",
+ "integrity": "sha512-9QqOYGuh5tZ76OzaT68kwI78AH+5lS/uZGGvkfxb3fc8FzRrIz2jOufNTliEBEeSAwmgK2rWLNsK+IB3zbtNPA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.974.17",
- "@aws-sdk/nested-clients": "^3.997.15",
- "@aws-sdk/token-providers": "3.1060.0",
- "@aws-sdk/types": "^3.973.10",
+ "@aws-sdk/core": "^3.974.18",
+ "@aws-sdk/nested-clients": "^3.997.17",
+ "@aws-sdk/token-providers": "3.1063.0",
+ "@aws-sdk/types": "^3.973.11",
"@smithy/core": "^3.24.6",
"@smithy/types": "^4.14.3",
"tslib": "^2.6.2"
@@ -602,14 +599,14 @@
}
},
"node_modules/@aws-sdk/credential-provider-web-identity": {
- "version": "3.972.47",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.47.tgz",
- "integrity": "sha512-eksfbUErOejUAGWBAcNqaP7IX21oUOEo73d9R56k9Ua4d57qS90NEYkWJsuSGzTXMFulCu17qXJI/qGmM7hvoA==",
+ "version": "3.972.49",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.49.tgz",
+ "integrity": "sha512-IYx1lN38MnnPXv+NBLpuATu0cZakbZ321TAfjW+aVkw7HIJF38YnEwdeEO55MSl3pl7hIX1IvvnD6EmnAzmAJw==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.974.17",
- "@aws-sdk/nested-clients": "^3.997.15",
- "@aws-sdk/types": "^3.973.10",
+ "@aws-sdk/core": "^3.974.18",
+ "@aws-sdk/nested-clients": "^3.997.17",
+ "@aws-sdk/types": "^3.973.11",
"@smithy/core": "^3.24.6",
"@smithy/types": "^4.14.3",
"tslib": "^2.6.2"
@@ -618,52 +615,13 @@
"node": ">=20.0.0"
}
},
- "node_modules/@aws-sdk/middleware-bucket-endpoint": {
- "version": "3.972.20",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.20.tgz",
- "integrity": "sha512-D35MfedGvTTzK1oygFPjm7DViSJwj9cuPV26ElHKwZqEz2rWag1hzYeAQ7st0jlCIAAihQgOyQ0/JwmqLOOinw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/middleware-sdk-s3": "^3.972.47",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-expect-continue": {
- "version": "3.972.16",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.16.tgz",
- "integrity": "sha512-S52iw+M9zJC+7uxRdvvKeiR0s2PDeYEmbNZQkWE6OJf8upIs+r4WQY0TER+6akVitEMeRdwS0DrBUhKkmpsyng==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/middleware-sdk-s3": "^3.972.47",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
"node_modules/@aws-sdk/middleware-flexible-checksums": {
- "version": "3.974.26",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.26.tgz",
- "integrity": "sha512-WndRXQV8wAU/bW3GH8THumEOSV7FpS0AtoluT2M7lYaaDUyG0gOCD+DppB+IWQ4TPmzuTtFcCedh9xCzM4Zv4g==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/checksums": "^3.1000.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-location-constraint": {
- "version": "3.972.13",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.13.tgz",
- "integrity": "sha512-Yh0MmpADMsSR7ExRM/2w85D26i/U2aDC/pC7fMwhUpmOl6sebGpmBPoRL/uJRDhqRrwX/tvXWWZrsbsPM/O9FQ==",
+ "version": "3.974.27",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.27.tgz",
+ "integrity": "sha512-bZqezPLdllFC4VAeV/f+EIc/hz56ab3TD/+4zNCgOgmG5ZHAE5dMHrX1gtTwdcQXbPr3KR7x3zTC3zuCTE6+ng==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/middleware-sdk-s3": "^3.972.47",
+ "@aws-sdk/checksums": "^3.1000.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -671,14 +629,14 @@
}
},
"node_modules/@aws-sdk/middleware-sdk-s3": {
- "version": "3.972.47",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.47.tgz",
- "integrity": "sha512-fzVBvGib8P1G6RFV3qVTPlXy9bMFAy5nxhdhA7LwyhWjRkJufNfJIPiloZq2mt36YAXSlLsEa4s3Kgcw6cv3+g==",
+ "version": "3.972.48",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.48.tgz",
+ "integrity": "sha512-MRTqx8wD/T3REt6LTT3/yN8rrp6+xIHrbUekkDYJTYWVch70mwtdJBovR4qKJz1jIPlbN+9R/Sn6R04BfsglzA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.974.17",
- "@aws-sdk/signature-v4-multi-region": "^3.996.31",
- "@aws-sdk/types": "^3.973.10",
+ "@aws-sdk/core": "^3.974.18",
+ "@aws-sdk/signature-v4-multi-region": "^3.996.32",
+ "@aws-sdk/types": "^3.973.11",
"@smithy/core": "^3.24.6",
"@smithy/types": "^4.14.3",
"tslib": "^2.6.2"
@@ -687,30 +645,17 @@
"node": ">=20.0.0"
}
},
- "node_modules/@aws-sdk/middleware-ssec": {
- "version": "3.972.13",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.13.tgz",
- "integrity": "sha512-M+dDhWp2zv9u92I4/4rgUFdiF8jSIk5PIj5ktyBdhvR/dkmKSYMo07nuh+3g8/59HnizwkcRC3glcLMX5GhyaQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/middleware-sdk-s3": "^3.972.47",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
"node_modules/@aws-sdk/nested-clients": {
- "version": "3.997.15",
- "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.15.tgz",
- "integrity": "sha512-Fpri1/PXKMKveORZ7E00VLTlWS5DkfZkW70PUE+bOnpWpAeHAQLoiDHhkzN3kNWbbSsGg64+IZYiq/EZgME3Mg==",
+ "version": "3.997.17",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.17.tgz",
+ "integrity": "sha512-lDRgraoTfKRawUyc176Ow93mrNrOho/x+EoK4C+lKU+vKkHWhNhzvSMVAx0WEJUJoeQxxDN5ZdKMfiGEyNejig==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "^3.974.17",
- "@aws-sdk/signature-v4-multi-region": "^3.996.31",
- "@aws-sdk/types": "^3.973.10",
+ "@aws-sdk/core": "^3.974.18",
+ "@aws-sdk/signature-v4-multi-region": "^3.996.32",
+ "@aws-sdk/types": "^3.973.11",
"@smithy/core": "^3.24.6",
"@smithy/fetch-http-handler": "^5.4.6",
"@smithy/node-http-handler": "^4.7.6",
@@ -722,12 +667,12 @@
}
},
"node_modules/@aws-sdk/signature-v4-multi-region": {
- "version": "3.996.31",
- "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.31.tgz",
- "integrity": "sha512-Kn2up9SlG1KC6wRtwf0d7waTGF6rvp9DxYqB54x6UCKdQ6kyaXCqHL4WGb5vUJga5kS8FxnjhY0LqM28aMvnNQ==",
+ "version": "3.996.32",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.32.tgz",
+ "integrity": "sha512-llvApLcsWtmRFhG2wT3WIp1CmDeRaIYutqty1ZZXoMzK7TiJ6MOLOimk9eXUS8PwgG4ew4pa4QAbt0lfhn++1w==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "^3.973.10",
+ "@aws-sdk/types": "^3.973.11",
"@smithy/signature-v4": "^5.4.6",
"@smithy/types": "^4.14.3",
"tslib": "^2.6.2"
@@ -737,14 +682,14 @@
}
},
"node_modules/@aws-sdk/token-providers": {
- "version": "3.1060.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1060.0.tgz",
- "integrity": "sha512-6NZaMKkFhpaNiwLpHi1sZaYjidL/lCJE6ME6NxwA8gv9vQna+Kr0j4OFwVoz6tANRWM3WbGz6jiPsGX/Vkjwow==",
+ "version": "3.1063.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1063.0.tgz",
+ "integrity": "sha512-nYDaWWdzjKiDP5xj8k4oUgcYd4WPgzfAOgdU5vJsaqH/07Dfvm7ffisHCFJ+NEl7kUC9JEIUxh0kznvenbo3NQ==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.974.17",
- "@aws-sdk/nested-clients": "^3.997.15",
- "@aws-sdk/types": "^3.973.10",
+ "@aws-sdk/core": "^3.974.18",
+ "@aws-sdk/nested-clients": "^3.997.17",
+ "@aws-sdk/types": "^3.973.11",
"@smithy/core": "^3.24.6",
"@smithy/types": "^4.14.3",
"tslib": "^2.6.2"
@@ -754,9 +699,9 @@
}
},
"node_modules/@aws-sdk/types": {
- "version": "3.973.10",
- "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.10.tgz",
- "integrity": "sha512-992QrTO7G9qCvKD0fx1rMlqcL14plUcRAbwmqqYVsuF3GrqcvlAL9qxR+baMafarEZ+l7DUQ5lCMmt5mbMhF7g==",
+ "version": "3.973.11",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.11.tgz",
+ "integrity": "sha512-YjS0qFuECClRh4qhEyW8XagW0fwEPBeZ1cfsW/gU73Kh/ExFILxbzxOfPCmzF/2DwEvhvsHYt0b0qnvStwKYrg==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/types": "^4.14.3",
@@ -767,9 +712,9 @@
}
},
"node_modules/@aws-sdk/util-locate-window": {
- "version": "3.965.5",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz",
- "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==",
+ "version": "3.965.6",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.6.tgz",
+ "integrity": "sha512-ZfHjfwSzeXj+Lg9AK5ZNmeDkXev6V+w2tn1t4kgDdRtUaRCthepTQiFwbD06EF9oNGH4LaLg+Mb6U16Ypv5bSw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -779,9 +724,9 @@
}
},
"node_modules/@aws-sdk/xml-builder": {
- "version": "3.972.27",
- "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.27.tgz",
- "integrity": "sha512-hpsCXCOI436kxWpjtRuIHVvuPP81MOw8f18jzfZeg+UOiiOvlqWcmWChzEhJEu16cOC6+ku4ncBN+7rdt+DZ9g==",
+ "version": "3.972.28",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.28.tgz",
+ "integrity": "sha512-lI/l3c/vPvsxmspzV63NfS3x9q4CkMmdhJy4QiM+NThAufVkDvi/PZZQ6xETnICL0UD7jI808pY83gllf86RFg==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/types": "^4.14.3",
@@ -1879,9 +1824,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "20.19.41",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz",
- "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==",
+ "version": "20.19.42",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.42.tgz",
+ "integrity": "sha512-5L7SUaFC1RyDraj2yRhyBzHTobyXHmohD100CChNtyPyleoq37Mqab5Gn8XEKI04dfN/oqPdpHk38MgcQWHbZg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2349,9 +2294,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.10.33",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz",
- "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==",
+ "version": "2.10.34",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.34.tgz",
+ "integrity": "sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -2476,9 +2421,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001793",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
- "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==",
+ "version": "1.0.30001797",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz",
+ "integrity": "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==",
"dev": true,
"funding": [
{
@@ -2775,9 +2720,9 @@
"license": "ISC"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.367",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.367.tgz",
- "integrity": "sha512-4Mk/mrynCNQ+atY40D3UpmhLWB6AHMbYMlIrPhHcMF6x0L7O0b052FCAsxw1LlaR++UFuNg3D/A6XCuGDa0guQ==",
+ "version": "1.5.368",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.368.tgz",
+ "integrity": "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==",
"dev": true,
"license": "ISC"
},
@@ -4699,11 +4644,10 @@
}
},
"node_modules/protobufjs": {
- "version": "8.5.0",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.5.0.tgz",
- "integrity": "sha512-df1jWDPA5VIBNRtuAHjqr09f2qN5D4Vke1wYqOQg1XJ7ZDpA7BD6L7E4tyChgGRLB5hqk2m79Zsy0WHwV9a84A==",
+ "version": "8.6.1",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.6.1.tgz",
+ "integrity": "sha512-s4qQPr4pU0W95iYnUInh95skjIg+3aM2sakYsw60QYanU+qWRDY2zQxOAQV6zU7ROJpSNDG9B+VSmk4dqdWWSA==",
"dev": true,
- "hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"long": "^5.3.2"
@@ -5841,6 +5785,19 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zx": {
+ "version": "8.8.5",
+ "resolved": "https://registry.npmjs.org/zx/-/zx-8.8.5.tgz",
+ "integrity": "sha512-SNgDF5L0gfN7FwVOdEFguY3orU5AkfFZm9B5YSHog/UDHv+lvmd82ZAsOenOkQixigwH2+yyH198AwNdKhj+RA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "zx": "build/cli.js"
+ },
+ "engines": {
+ "node": ">= 12.17.0"
+ }
}
}
}
diff --git a/package.json b/package.json
index 386af58..51b8c0a 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,9 @@
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
- "format": "prettier --plugin-search-dir . --write ."
+ "format": "prettier --plugin-search-dir . --write .",
+ "upload-examples": "zx scripts/upload-examples.mjs",
+ "upload-all": "zx scripts/upload-examples.mjs --assets"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
@@ -38,7 +40,8 @@
"tslib": "^2.4.1",
"typescript": "~5.4.0",
"vite": "^4.3.0",
- "vite-plugin-static-copy": "^0.15.0"
+ "vite-plugin-static-copy": "^0.15.0",
+ "zx": "^8.8.5"
},
"type": "module",
"dependencies": {
diff --git a/release-please-config.json b/release-please-config.json
index e1b178b..f310b21 100644
--- a/release-please-config.json
+++ b/release-please-config.json
@@ -1,8 +1,8 @@
{
- "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
- "release-type": "node",
- "include-component-in-tag": false,
- "packages": {
- ".": {}
- }
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
+ "release-type": "node",
+ "include-component-in-tag": false,
+ "packages": {
+ ".": {}
+ }
}
diff --git a/scripts/upload-examples.mjs b/scripts/upload-examples.mjs
new file mode 100644
index 0000000..79486a7
--- /dev/null
+++ b/scripts/upload-examples.mjs
@@ -0,0 +1,109 @@
+#!/usr/bin/env zx
+// Upload all files under examples/ to S3 using @aws-sdk/client-s3.
+// New and changed files (MD5 vs S3 ETag) are uploaded; identical files are skipped.
+// Reads AWS_* env vars (same as the app).
+//
+// Usage:
+// npx zx scripts/upload-examples.mjs [--assets|-a] [examples-dir]
+//
+// --assets / -a also upload the examples/assets/ folder (omit to skip it)
+
+import { createHash } from 'node:crypto';
+import { fileURLToPath } from 'node:url';
+import { paginateListObjectsV2, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
+import { argv, dotenv, echo, fs, glob, path } from 'zx';
+
+const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
+
+const envFile = path.join(repoRoot, '.env');
+if (fs.existsSync(envFile)) dotenv.config(envFile);
+
+const includeAssets = argv.assets || argv.a || false;
+const examplesDir = argv._[0] ? path.resolve(argv._[0]) : path.join(repoRoot, 'examples');
+const parallelism = Number(process.env.UPLOAD_PARALLELISM ?? 32);
+
+const bucket = process.env.AWS_BUCKET;
+if (!bucket) {
+ echo('ERROR: AWS_BUCKET is required');
+ process.exit(1);
+}
+if (!(await fs.pathExists(examplesDir))) {
+ echo(`ERROR: examples directory not found: ${examplesDir}`);
+ process.exit(1);
+}
+
+const client = new S3Client({
+ endpoint: process.env.AWS_ENDPOINT_URL,
+ forcePathStyle: true,
+ region: process.env.AWS_REGION ?? 'us-east-1'
+});
+
+const CONTENT_TYPES = {
+ '.html': 'text/html',
+ '.css': 'text/css',
+ '.js': 'text/javascript',
+ '.json': 'application/json',
+ '.txt': 'text/plain',
+ '.png': 'image/png',
+ '.jpg': 'image/jpeg',
+ '.gif': 'image/gif'
+};
+
+// Fetch all existing S3 keys + ETags.
+echo('Fetching existing S3 objects...');
+const etagByKey = new Map();
+for await (const page of paginateListObjectsV2({ client }, { Bucket: bucket })) {
+ for (const { Key, ETag } of page.Contents ?? []) {
+ etagByKey.set(Key, ETag.replaceAll('"', ''));
+ }
+}
+echo(`Found ${etagByKey.size} existing objects in S3.\n`);
+
+// Collect local files whose MD5 differs from the S3 ETag.
+// ETag equals MD5 for non-multipart uploads, which holds for everything PutObject sends.
+const files = await glob('**/*', {
+ cwd: examplesDir,
+ ignore: includeAssets ? [] : ['assets/**']
+});
+
+const toUpload = [];
+for (const key of files.sort()) {
+ const body = await fs.readFile(path.join(examplesDir, key));
+ const md5 = createHash('md5').update(body).digest('hex');
+ if (etagByKey.get(key) !== md5) {
+ toUpload.push({ key, body, isNew: !etagByKey.has(key) });
+ }
+}
+echo(`${files.length - toUpload.length} files unchanged, ${toUpload.length} to upload.\n`);
+
+// Promise pool: `parallelism` workers pulling from a shared iterator.
+const counts = { uploaded: 0, updated: 0, failed: 0 };
+const iter = toUpload[Symbol.iterator]();
+await Promise.all(
+ Array.from({ length: parallelism }, async () => {
+ for (const { key, body, isNew } of iter) {
+ try {
+ await client.send(
+ new PutObjectCommand({
+ Bucket: bucket,
+ Key: key,
+ Body: body,
+ ContentType: CONTENT_TYPES[path.extname(key)] ?? 'application/octet-stream'
+ })
+ );
+ echo(`${isNew ? 'UP' : 'UPDATE'} ${key}`);
+ counts[isNew ? 'uploaded' : 'updated']++;
+ } catch (err) {
+ echo(`FAIL ${key} (${err.message ?? err})`);
+ counts.failed++;
+ }
+ }
+ })
+);
+
+echo(
+ `\nDone — uploaded: ${counts.uploaded} new, ${counts.updated} updated, ${
+ files.length - toUpload.length
+ } skipped, ${counts.failed} failed`
+);
+if (counts.failed > 0) process.exit(1);
diff --git a/src/lib/components/bottomBar.svelte b/src/lib/components/bottomBar.svelte
index 0971050..3ceec36 100644
--- a/src/lib/components/bottomBar.svelte
+++ b/src/lib/components/bottomBar.svelte
@@ -7,6 +7,7 @@
BottomNavHeaderItem
} from 'flowbite-svelte';
import { goto } from '$app/navigation';
+ import { page } from '$app/stores';
import BottomHeaderItem from './bottomHeaderItem.svelte';
import classNames from 'classnames';
@@ -14,6 +15,8 @@
export let items: { name: string; displayName?: string }[];
export let activeClient: string | undefined;
+ $: activeItem = $page.params.name;
+
$: outerDiv = classNames('-translate-x-0', 'dark:bg-gray-800', $$props.outerDiv);
@@ -41,7 +44,12 @@
goto('/demo/' + activeClient + '/' + item.name)}
id="group-{item.name}"
- btnDefault="basis-0 items-center justify-center ml-[4px] mb-1 bg-gray-100 dark:bg-gray-600 rounded-lg p-4 text-gray-900 hover:bg-gray-200 dark:text-white dark:hover:bg-gray-700 group"
+ btnDefault={classNames(
+ 'basis-0 items-center justify-center ml-[4px] mb-1 rounded-lg p-4 group',
+ item.name === activeItem
+ ? 'bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600'
+ : 'bg-gray-100 dark:bg-gray-600 text-gray-900 hover:bg-gray-200 dark:text-white dark:hover:bg-gray-700'
+ )}
>
{item.displayName || item.name}