diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a22f4cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +.timestamp-install + diff --git a/Dockerfile b/Dockerfile index 007cc65..0fc8dc6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,34 @@ -ARG NODE_VERSION="16-slim" -FROM registry.puzzle.ch/docker.io/node:${NODE_VERSION} as dev +ARG NODE_VERSION="18.10" +FROM node:${NODE_VERSION} as qwc2_base + +ARG QWC2_VERSION_HASH="86ba224001cd3c9813ad645f4ccf4de7a17db801" +ARG QWC2_DOWNLOAD_PATH="https://github.com/qgis/qwc2/archive/$QWC2_VERSION_HASH.zip" +RUN apt-get update && apt-get install -y \ + bash \ + curl && \ + curl -L -O $QWC2_DOWNLOAD_PATH && \ + unzip $QWC2_VERSION_HASH.zip && \ + rm $QWC2_VERSION_HASH.zip && \ + mv qwc2-$QWC2_VERSION_HASH /qwc2 && \ + echo "$QWC2_VERSION_HASH" > /qwc2/.qwc2.version.txt && \ + echo "$QWC2_DOWNLOAD_PATH" > /qwc2/.qwc2.download_source.txt + +COPY ./app/.yarnrc /qwc2 + +WORKDIR /qwc2 + +RUN yarn install + +FROM qwc2_base as dev + +ENV DEV_SERVER_PORT=8080 -STOPSIGNAL SIGINT -RUN apt update && apt install -y \ - make \ - bash \ - git WORKDIR /app +STOPSIGNAL SIGINT +RUN apt-get update && apt-get install -y \ + make + CMD ["/usr/bin/make", "clean", "serve-dev"] #============== @@ -16,7 +37,7 @@ CMD ["/usr/bin/make", "clean", "serve-dev"] FROM dev as builder -COPY app/ /app +COPY ./app /app WORKDIR /app @@ -26,7 +47,7 @@ RUN /usr/bin/make clean build # Stage prod #============== -FROM registry.puzzle.ch/docker.io/nginx:stable as prod +FROM nginx:1.23.2 as prod COPY --from=builder /app/prod /usr/share/nginx/html - +COPY ./example_config/config.json ./example_config/themes.json /usr/share/nginx/html/ diff --git a/README.md b/README.md index aabbceb..b7bb751 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,19 @@ Administration => Kunde/opengis Rechte Daten Konfiguration - \ No newline at end of file + +```shell +docker build -t qwc2_minimal:latest --target prod . +docker build -t qwc2_minimal:latest-builder --target builder . +``` + +```shell +docker run --rm -v $(pwd)/example_config/themes.json:/app/static/themes.json -v $(pwd)/example_config/themesConfig.json:/app/themesConfig.json -u $(id -u):$(id -g) qwc2_minimal:latest-builder npm run themesconfig +``` + + +```shell +docker run --rm -p 80:80 qwc2_minimal:latest +``` + + diff --git a/app/.gitattributes b/app/.gitattributes new file mode 100644 index 0000000..93a3de0 --- /dev/null +++ b/app/.gitattributes @@ -0,0 +1,3 @@ +*.png binary +*.ico binary +*.jpg binary diff --git a/app/.gitignore b/app/.gitignore index 9b736be..4f33f58 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -3,6 +3,5 @@ dist/ prod/ icons/build npm-debug.log -themes.json static/assets/img/genmapthumbs/* yarn-error.log diff --git a/app/.yarnrc b/app/.yarnrc new file mode 100644 index 0000000..04aaf4e --- /dev/null +++ b/app/.yarnrc @@ -0,0 +1 @@ +--modules-folder /node_modules diff --git a/app/Makefile b/app/Makefile index c8aff67..be78791 100644 --- a/app/Makefile +++ b/app/Makefile @@ -1,24 +1,11 @@ -QWC2_VERSION_HASH="86ba224001cd3c9813ad645f4ccf4de7a17db801" -QWC2_REPO_URL="https://github.com/qgis/qwc2.git" -QWC2_FOLDER="qwc2" -qwc2/.timestamp-clone: - git clone $(QWC2_REPO_URL) $(QWC2_FOLDER) - touch $@ - -qwc2/.timestamp-checkout: qwc2/.timestamp-clone - cd $(QWC2_FOLDER) && git checkout $(QWC2_VERSION_HASH) - -node_modules/.timestamp-install: qwc2/.timestamp-checkout - yarn install - touch $@ - -prod/.timestamp-build: node_modules/.timestamp-install - npm run tsupdate - npm run iconfont -# npm run themesconfig +QWC2_VERSION_HASH=$(shell cat /qwc2/.qwc2.version.txt) +QWC2_REPO_URL=$(shell cat /qwc2/.qwc2.download_source.txt) - node_modules/webpack/bin/webpack.js --mode production --progress +prod/.timestamp-build: install + node /qwc2/scripts/updateTranslations.js + node /qwc2/scripts/makeIconkit.js + /node_modules/webpack/bin/webpack.js --mode production --progress echo "" >> ./prod/index.html echo "" >> ./prod/index.html echo "" >> ./prod/index.html @@ -28,11 +15,14 @@ prod/.timestamp-build: node_modules/.timestamp-install touch $@ .PHONY: install -install: node_modules/.timestamp-install +install: package.json + yarn install .PHONY: serve-dev serve-dev: install - yarn start + node /qwc2/scripts/updateTranslations.js + node /qwc2/scripts/makeIconkit.js + /node_modules/webpack/bin/webpack.js serve --mode development --progress --host 0.0.0.0 --port $(DEV_SERVER_PORT) .PHONY: clean clean: @@ -44,3 +34,7 @@ clean: .PHONY: build build: prod/.timestamp-build + +.PHONY: generate-themes +generate-themes: themesConfig.json + node /qwc2/scripts/themesConfig.js diff --git a/app/package.json b/app/package.json index c443c14..2001ca0 100644 --- a/app/package.json +++ b/app/package.json @@ -1,39 +1,39 @@ { - "name": "QWC2App", - "version": "2021.11.24", - "description": "QGIS Web Client 2 application", - "author": "Sourcepole", - "license": "BSD-2-Clause", - "repository": "git@github.com:sourcepole/qwc2-demo-app.git", - "private": true, - "workspaces": [ - "qwc2" - ], - "devDependencies": { - "babel-loader": "8.2.3", - "css-loader": "6.4.0", - "eslint": "7.32.0", - "eslint-plugin-react": "7.26.1", - "source-map-loader": "3.0.0", - "string-replace-loader": "3.0.3", - "style-loader": "3.3.1", - "webpack": "5.59.1", - "webpack-bundle-size-analyzer": "3.1.0", - "webpack-cli": "4.9.1", - "webpack-dev-server": "4.3.1", - "html-webpack-plugin": "5.4.0", - "clean-webpack-plugin": "4.0.0", - "copy-webpack-plugin": "9.0.0" - }, - "scripts": { - "prod": "webpack --mode production --progress", - "start": "webpack serve --mode development --progress --host 0.0.0.0 --port 8081", - "report": "webpack --profile -m static > stats.html", - "iconfont": "node qwc2/scripts/makeIconkit.js", - "themesconfig": "node qwc2/scripts/themesConfig.js", - "tsupdate": "node qwc2/scripts/updateTranslations.js", - "build": "npm run prod", - "analyze": "webpack --mode production --json | webpack-bundle-size-analyzer", - "release": "node -e \"process.exit(require('os').platform() === 'win32' ? 0 : 1)\" && qwc2\\scripts\\package-commands.bat release || ./qwc2/scripts/package-commands.sh release" - } + "name": "QWC2App", + "version": "2021.11.24", + "description": "QGIS Web Client 2 application", + "author": "Sourcepole", + "license": "BSD-2-Clause", + "repository": "git@github.com:sourcepole/qwc2-demo-app.git", + "private": true, + "workspaces": [ + "../qwc2" + ], + "devDependencies": { + "babel-loader": "8.2.3", + "css-loader": "6.4.0", + "eslint": "7.32.0", + "eslint-plugin-react": "7.26.1", + "source-map-loader": "3.0.0", + "string-replace-loader": "3.0.3", + "style-loader": "3.3.1", + "webpack": "5.59.1", + "webpack-bundle-size-analyzer": "3.1.0", + "webpack-cli": "4.9.1", + "webpack-dev-server": "4.3.1", + "html-webpack-plugin": "5.4.0", + "clean-webpack-plugin": "4.0.0", + "copy-webpack-plugin": "9.0.0" + }, + "scripts": { + "analyze": "/node_modules/webpack/bin/webpack.js --mode production --json | webpack-bundle-size-analyzer", + "start": "/node_modules/webpack/bin/webpack.js serve --mode development --progress --host 0.0.0.0 --port 8081", + "build": "npm run prod", + "prod": "/node_modules/webpack/bin/webpack.js --mode production --progress", + "release": "/qwc2/scripts/package-commands.sh release", + "report": "/node_modules/webpack/bin/webpack.js --profile -m static > stats.html", + "themesconfig": "node /qwc2/scripts/themesConfig.js", + "tsupdate": "node /qwc2/scripts/updateTranslations.js", + "iconfont": "node /qwc2/scripts/makeIconkit.js" + } } diff --git a/app/static/assets/img/mapthumbs/bauprojekte.png b/app/static/assets/img/mapthumbs/bauprojekte.png new file mode 100644 index 0000000..e46e57d Binary files /dev/null and b/app/static/assets/img/mapthumbs/bauprojekte.png differ diff --git a/themes.json b/app/static/themes.json similarity index 70% rename from themes.json rename to app/static/themes.json index d8048d1..73fa68b 100644 --- a/themes.json +++ b/app/static/themes.json @@ -1,7 +1,268 @@ { "themes": { "title": "root", - "subdirs": [], + "subdirs": [ + { + "id": "g1", + "title": "Dev Tests", + "items": [ + { + "url": "http://qwc2.sourcepole.ch/ows/qwc_demo", + "id": "qwc_demo", + "name": "qwc_demo", + "title": "Demo", + "description": "", + "attribution": { + "Title": "Demo attribution", + "OnlineResource": "https://127.0.0.1/" + }, + "abstract": "", + "keywords": "", + "onlineResource": "http://qwc2.sourcepole.ch/ows/qwc_demo", + "contact": { + "person": "", + "organization": "", + "position": "", + "phone": "", + "email": "" + }, + "availableFormats": [ + "image/jpeg", + "image/png", + "image/png; mode=16bit", + "image/png; mode=8bit", + "image/png; mode=1bit", + "application/dxf" + ], + "version": "1.3.0", + "infoFormats": [ + "text/plain", + "text/html", + "text/xml", + "application/vnd.ogc.gml", + "application/json", + "application/geo+json" + ], + "bbox": { + "crs": "EPSG:4326", + "bounds": [ + -179.999996, + -89, + 179.999996, + 89 + ] + }, + "initialBbox": { + "crs": "EPSG:3857", + "bounds": [ + -1000000, + 4000000, + 3000000, + 8000000 + ] + }, + "printResolutions": [ + 300 + ], + "sublayers": [ + { + "name": "edit_demo", + "title": "Edit Demo", + "mutuallyExclusive": false, + "sublayers": [ + { + "name": "edit_points", + "title": "Edit Points", + "geometryType": "Point", + "visibility": true, + "queryable": true, + "displayField": "name", + "opacity": 255, + "bbox": { + "crs": "EPSG:4326", + "bounds": [ + -4.16051, + -7.109603, + 77.999658, + 59.442792 + ] + }, + "dimensions": [] + }, + { + "name": "edit_lines", + "title": "Edit Lines", + "geometryType": "LineString", + "visibility": true, + "queryable": true, + "displayField": "name", + "opacity": 255, + "bbox": { + "crs": "EPSG:4326", + "bounds": [ + -104.062275, + 20.280322, + 24.719663, + 59.367121 + ] + }, + "dimensions": [] + }, + { + "name": "edit_polygons", + "title": "Edit Polygons", + "geometryType": "Polygon", + "visibility": true, + "queryable": true, + "displayField": "name", + "opacity": 255, + "bbox": { + "crs": "EPSG:4326", + "bounds": [ + -179.536896, + -21.987341, + 175.711895, + 59.44345 + ] + }, + "dimensions": [] + } + ], + "expanded": true + }, + { + "name": "geographic_lines", + "title": "Geographic lines", + "geometryType": "MultiLineString", + "visibility": true, + "queryable": true, + "displayField": "name", + "opacity": 255, + "bbox": { + "crs": "EPSG:4326", + "bounds": [ + -180, + -66.549927, + 179.999997, + 66.549986 + ] + }, + "dimensions": [] + }, + { + "name": "country_names", + "title": "Country names", + "geometryType": "Point", + "visibility": true, + "queryable": true, + "displayField": "z_name", + "opacity": 255, + "minScale": 0, + "maxScale": 10000000, + "bbox": { + "crs": "EPSG:4326", + "bounds": [ + -177.22902, + -80.516568, + 178.519895, + 73.34887 + ] + }, + "dimensions": [] + }, + { + "name": "states_provinces", + "title": "States and Provinces", + "geometryType": "MultiLineString", + "visibility": true, + "queryable": true, + "displayField": "name", + "opacity": 255, + "minScale": 0, + "maxScale": 5000000, + "bbox": { + "crs": "EPSG:4326", + "bounds": [ + -178.137086, + -49.250871, + 178.448623, + 81.128532 + ] + }, + "dimensions": [] + }, + { + "name": "countries", + "title": "Countries", + "geometryType": "MultiPolygon", + "visibility": true, + "queryable": true, + "displayField": "name", + "opacity": 255, + "bbox": { + "crs": "EPSG:4326", + "bounds": [ + -180, + -89.501386, + 180, + 83.634101 + ] + }, + "dimensions": [] + } + ], + "expanded": true, + "externalLayers": [], + "backgroundLayers": [ + { + "name": "bluemarble", + "printLayer": "bluemarble_bg", + "visibility": true + }, + { + "name": "mapnik", + "printLayer": "osm_bg" + } + ], + "searchProviders": [ + "coordinates", + "nominatim" + ], + "additionalMouseCrs": [], + "mapCrs": "EPSG:3857", + "print": [ + { + "name": "A4 Landscape", + "map": { + "name": "map0", + "width": 220, + "height": 190 + }, + "labels": [ + "user_text" + ] + } + ], + "drawingOrder": [ + "countries", + "states_provinces", + "country_names", + "geographic_lines", + "edit_polygons", + "edit_lines", + "edit_points" + ], + "legendUrl": "http://qwc2.sourcepole.ch/api/v1/legend/qwc_demo?", + "featureInfoUrl": "http://qwc2.sourcepole.ch/api/v1/featureinfo/qwc_demo?", + "printUrl": "http://qwc2.sourcepole.ch/ows/qwc_demo?", + "skipEmptyFeatureAttributes": true, + "editConfig": null, + "thumbnail": "img/mapthumbs/default.jpg" + } + ], + "subdirs": [] + } + ], "items": [ { "url": "http://qwc2.sourcepole.ch/ows/uster/bauprojekte", @@ -219,260 +480,7 @@ "featureInfoUrl": "http://qwc2.sourcepole.ch/api/v1/featureinfo/uster/bauprojekte?", "printUrl": "http://qwc2.sourcepole.ch/ows/uster/bauprojekte?", "editConfig": null, - "thumbnail": "img/mapthumbs/default.jpg" - }, - { - "url": "http://qwc2.sourcepole.ch/ows/qwc_demo", - "id": "qwc_demo", - "name": "qwc_demo", - "title": "Demo", - "description": "", - "attribution": { - "Title": "Demo attribution", - "OnlineResource": "https://127.0.0.1/" - }, - "abstract": "", - "keywords": "", - "onlineResource": "http://qwc2.sourcepole.ch/ows/qwc_demo", - "contact": { - "person": "", - "organization": "", - "position": "", - "phone": "", - "email": "" - }, - "availableFormats": [ - "image/jpeg", - "image/png", - "image/png; mode=16bit", - "image/png; mode=8bit", - "image/png; mode=1bit", - "application/dxf" - ], - "version": "1.3.0", - "infoFormats": [ - "text/plain", - "text/html", - "text/xml", - "application/vnd.ogc.gml", - "application/json", - "application/geo+json" - ], - "bbox": { - "crs": "EPSG:4326", - "bounds": [ - -179.999996, - -89, - 179.999996, - 89 - ] - }, - "initialBbox": { - "crs": "EPSG:3857", - "bounds": [ - -1000000, - 4000000, - 3000000, - 8000000 - ] - }, - "printResolutions": [ - 300 - ], - "sublayers": [ - { - "name": "edit_demo", - "title": "Edit Demo", - "mutuallyExclusive": false, - "sublayers": [ - { - "name": "edit_points", - "title": "Edit Points", - "geometryType": "Point", - "visibility": true, - "queryable": true, - "displayField": "name", - "opacity": 255, - "bbox": { - "crs": "EPSG:4326", - "bounds": [ - -4.16051, - -7.109603, - 77.999658, - 51.889035 - ] - }, - "dimensions": [] - }, - { - "name": "edit_lines", - "title": "Edit Lines", - "geometryType": "LineString", - "visibility": true, - "queryable": true, - "displayField": "name", - "opacity": 255, - "bbox": { - "crs": "EPSG:4326", - "bounds": [ - -179.999996, - -89, - 179.999996, - 89 - ] - }, - "dimensions": [] - }, - { - "name": "edit_polygons", - "title": "Edit Polygons", - "geometryType": "Polygon", - "visibility": true, - "queryable": true, - "displayField": "name", - "opacity": 255, - "bbox": { - "crs": "EPSG:4326", - "bounds": [ - -177.262003, - -21.987341, - 178.605868, - 47.378912 - ] - }, - "dimensions": [] - } - ], - "expanded": true - }, - { - "name": "geographic_lines", - "title": "Geographic lines", - "geometryType": "MultiLineString", - "visibility": true, - "queryable": true, - "displayField": "name", - "opacity": 255, - "bbox": { - "crs": "EPSG:4326", - "bounds": [ - -180, - -66.549927, - 179.999997, - 66.549986 - ] - }, - "dimensions": [] - }, - { - "name": "country_names", - "title": "Country names", - "geometryType": "Point", - "visibility": true, - "queryable": true, - "displayField": "z_name", - "opacity": 255, - "minScale": 0, - "maxScale": 10000000, - "bbox": { - "crs": "EPSG:4326", - "bounds": [ - -177.22902, - -80.516568, - 178.519895, - 73.34887 - ] - }, - "dimensions": [] - }, - { - "name": "states_provinces", - "title": "States and Provinces", - "geometryType": "MultiLineString", - "visibility": true, - "queryable": true, - "displayField": "name", - "opacity": 255, - "minScale": 0, - "maxScale": 5000000, - "bbox": { - "crs": "EPSG:4326", - "bounds": [ - -178.137086, - -49.250871, - 178.448623, - 81.128532 - ] - }, - "dimensions": [] - }, - { - "name": "countries", - "title": "Countries", - "geometryType": "MultiPolygon", - "visibility": true, - "queryable": true, - "displayField": "name", - "opacity": 255, - "bbox": { - "crs": "EPSG:4326", - "bounds": [ - -180, - -89.501386, - 180, - 83.634101 - ] - }, - "dimensions": [] - } - ], - "expanded": true, - "externalLayers": [], - "backgroundLayers": [ - { - "name": "bluemarble", - "printLayer": "bluemarble_bg", - "visibility": true - }, - { - "name": "mapnik", - "printLayer": "osm_bg" - } - ], - "searchProviders": [ - "coordinates", - "nominatim" - ], - "additionalMouseCrs": [], - "mapCrs": "EPSG:3857", - "print": [ - { - "name": "A4 Landscape", - "map": { - "name": "map0", - "width": 220, - "height": 190 - }, - "labels": [ - "user_text" - ] - } - ], - "drawingOrder": [ - "countries", - "states_provinces", - "country_names", - "geographic_lines", - "edit_polygons", - "edit_lines", - "edit_points" - ], - "legendUrl": "http://qwc2.sourcepole.ch/api/v1/legend/qwc_demo?", - "featureInfoUrl": "http://qwc2.sourcepole.ch/api/v1/featureinfo/qwc_demo?", - "printUrl": "http://qwc2.sourcepole.ch/ows/qwc_demo?", - "skipEmptyFeatureAttributes": true, - "editConfig": null, - "thumbnail": "img/mapthumbs/default.jpg" + "thumbnail": "img/genmapthumbs/bauprojekte.png" } ], "defaultTheme": "qwc_demo", diff --git a/app/webpack.config.js b/app/webpack.config.js index 1b4018a..595644e 100644 --- a/app/webpack.config.js +++ b/app/webpack.config.js @@ -37,8 +37,7 @@ module.exports = (env, argv) => { } ], compress: true, - hot: true, - port: 8080 + hot: true }, resolve: { extensions: [".mjs", ".js", ".jsx"], @@ -60,7 +59,7 @@ module.exports = (env, argv) => { BuildDate: JSON.stringify(buildDate) } }), - new webpack.NormalModuleReplacementPlugin(/openlayers$/, path.join(__dirname, "qwc2", "libs", "openlayers")), + new webpack.NormalModuleReplacementPlugin(/openlayers$/, "/qwc2/libs/openlayers"), new HtmlWebpackPlugin({ template: path.resolve(__dirname, "index.html"), build: buildDate, @@ -91,7 +90,7 @@ module.exports = (env, argv) => { exclude: /node_modules(\\|\/)(?!qwc2)/, use: { loader: 'babel-loader', - options: { babelrcRoots: ['.', path.resolve(__dirname, 'node_modules', 'qwc2')] } + options: { babelrcRoots: ['.', '/qwc2', '/node_modules'] } } }, { diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c2cda8d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.8" + +services: + qwc2-minimal: + build: + context: . + target: dev + ports: + - "8081:8081" + volumes: + - ./app:/app + - ./example_config/config.json:/usr/share/nginx/html/config.json + - ./example_config/themes.json:/usr/share/nginx/html/themes.json \ No newline at end of file diff --git a/docs/differences/DIFFERENCES.md b/docs/differences/DIFFERENCES.md new file mode 100644 index 0000000..f93b5de --- /dev/null +++ b/docs/differences/DIFFERENCES.md @@ -0,0 +1,12 @@ +# Differences to QWC2 demo app + +Due to the slightly different approach how to handle QWC2 and its parts there are some +differences to the setup as it is suggested in [QWC2 demo app](https://github.com/qgis/qwc2-demo-app). + +Node Modules in root path + +QWC2 in root path + +App in /app + + diff --git a/docs/differences/img/package.json.difference.png b/docs/differences/img/package.json.difference.png new file mode 100644 index 0000000..2c4fa6d Binary files /dev/null and b/docs/differences/img/package.json.difference.png differ diff --git a/docs/differences/img/webpack.config.js.difference.1.png b/docs/differences/img/webpack.config.js.difference.1.png new file mode 100644 index 0000000..c221619 Binary files /dev/null and b/docs/differences/img/webpack.config.js.difference.1.png differ diff --git a/docs/differences/img/webpack.config.js.difference.2.png b/docs/differences/img/webpack.config.js.difference.2.png new file mode 100644 index 0000000..d82bf59 Binary files /dev/null and b/docs/differences/img/webpack.config.js.difference.2.png differ diff --git a/docs/differences/img/webpack.config.js.difference.3.png b/docs/differences/img/webpack.config.js.difference.3.png new file mode 100644 index 0000000..58daaac Binary files /dev/null and b/docs/differences/img/webpack.config.js.difference.3.png differ diff --git a/docs/main/MAIN.md b/docs/main/MAIN.md new file mode 100644 index 0000000..518e817 --- /dev/null +++ b/docs/main/MAIN.md @@ -0,0 +1,142 @@ +# QWC2 minimal + +## Overview + +The regular [QWC2 Demo App](https://github.com/qgis/qwc2-demo-app) is a good point to start and +have a quick application running. But when it comes to deployment and to maintain code over +time in different projects it lacks a bit of organization. This is mainly introduced by two things: + +1. [QWC2 is a sub repository in this project](https://github.com/qgis/qwc2) +1. There is no packaging/release mechanism + +Both facts seem to stand in the way of rapid development. Despite they aren't. It is true that +building packages and managing dependencies is a pain. But not solving this inside the +project with all the knowledge of the developers who produce the code shifts the pain to +integrators or users. They do not have a clue about the software and therefore come up with silly +ideas how to solve things upstream. Creating high gain of noise and blocking DEVs from their real +work. So in the end the time meant to be saved is blocked just in another way. + +## This repository + +This approach tries to solve some of this pain. Even if it is not a final solution since it +only handles upstream. It does not manipulate it. + +It implements the following ideas: + +- inspired by the [QWC2 Demo App](https://github.com/qgis/qwc2-demo-app) this reflects an instance + of a WebGIS which might be adapted to further needs or can be served as is +- Everything is encapsulated in Docker +- Running any command should not leave any touched files but necessary ones on the host +- A DEV server to conveniently develop code locally +- Updating the basic lib [QWC2](https://github.com/qgis/qwc2) shouldn't pollute the implementation project +- the basic lib [QWC2](https://github.com/qgis/qwc2) is handled as a singleton +- Node modules are not polluting the host system + +It is meant to be used as is for really simple cases where we only want to offer a basic WebGIS +with some themes, layers, print templates, etc. It can be slightly adapted to the clients needs +as of the symbols and the cartographic content. + +As soon as there should be any custom functionality this repository should be used as a pattern +for a custom implementation. It's not worth to customize it directly. However, some parts may be +reused. Especially the Docker images described in the next section. + +## The Docker structure + +There are 4 Docker images/targets: + +1. qwc2_base +1. dev +1. builder +1. prod + +They depend on each other in the order stated above. + +### qwc2_base + +It includes the QWC2 code in a defined version (HASH) in the root folder `/qwc2` and all +the Node modules it depends on which are stored in the root folder `node_modules`. + +The main interesting point here is the [.yarnrc file](../../app/.yarnrc) which tells +Node to store the packages in a defined path. This file is copied to the image at build +time. + +It does not contain an instance of QWC2 which can be started. + +### dev + +It includes nothing but the things of the stage `qwc2_base`. It sets the working directory +to `app`. + +It is meant to be used as the development environment. The content of the `app` folder of +this repository should be mounted along with Docker run command: + +```shell +docker run --rm -p 8080:8080 -v $(pwd)/app:/app qwc2_minimal:latest-dev +``` + +That runs the stack in dev mode to develop things. It is useful to integrate with the newest +version of QWC2 since this often comes with changes in configuration and code. Using this +repository is a safe and local way to achieve this without manipulating clients infrastructure. + +### builder + +This stage is mainly used to prepare the static assets of the QWC2 app in a way that they can +be served by a simple webserver in the end. The result is used in the next stage. + +But it includes also the toolset of QWC2 which is for building fonts, icon sets +(not the clients custom icons), translations and the most important => the themes.json + +So we can use this Docker image directly to produce a `themes.json` out of a `themesConfig.json`. +Most use cases show needs that this `themesConfig.json` is handled by the client and heavily depends +on services running in the client's infrastructure. As a QGIS-Server accessing local databases etc. + +In this infrastructure we can use the `builder` to generate the artifact needed by QWC2 => `themes.json`: + +```shell +docker run --rm \ + -v $(pwd)/example_config/themes.json:/app/static/themes.json \ + -v $(pwd)/example_config/themesConfig.json:/app/themesConfig.json \ + -v $(pwd)/example_config/map_thumbs:/app/static/assets/img/genmapthumbs \ + -u $(id -u):$(id -g) \ + qwc2_minimal:latest-builder make generate-themes +``` + +Since QWC2 can also create preview thumbs for the theme selection tool we also mount that dir +to obtain generated thumbs. + +### prod + +> **NOTE**: You may want to delete or disable the cache of you browser to force load the correct files +> instead of using the browsers cache. + +It contains a simple nginx serving the static assets and so a fully working instance of QWC2 but without +any cartographic data. + +```shell +docker run --rm -p 80:80 qwc2_minimal:latest +``` + +However, this can be easily adapted to a clients setup where the client has an own `config.json` +to enable/disable things in the WebGIS (buttons, toolbars, etc.) and an own `themesConfig.json` +which was already *built* to a `themes.json`. + +To show how it might work this repository ships with an example set of such data in the folder +`example_config`. + +You might try to use that with the following Docker command: + +```shell +docker run --rm \ + -p 80:80 \ + -v $(pwd)/example_config/themes.json:/usr/share/nginx/html/themes.json \ + -v $(pwd)/example_config/config.json:/usr/share/nginx/html/config.json \ + -v $(pwd)/example_config/app_logos/logo.svg:/usr/share/nginx/html/assets/img/logo.svg \ + -v $(pwd)/example_config/app_logos/favicon.ico:/usr/share/nginx/html/assets/img/favicon.ico \ + qwc2_minimal:latest +``` + +TODO: add better map theme setup to describe how it works. + +Loading `http://localhost:80` in your browser will show an empty WebGIS (no cartographic data). This is not +different to the command above. But the WebGIS does have a top toolbar. This is because one was configured +in the `config.json`. You may also recognize the different logo in the header and for the favicon. diff --git a/example_config/app_logos/favicon.ico b/example_config/app_logos/favicon.ico new file mode 100644 index 0000000..ee951d5 Binary files /dev/null and b/example_config/app_logos/favicon.ico differ diff --git a/example_config/app_logos/logo.png b/example_config/app_logos/logo.png new file mode 100644 index 0000000..5d60e42 Binary files /dev/null and b/example_config/app_logos/logo.png differ diff --git a/example_config/app_logos/logo.svg b/example_config/app_logos/logo.svg new file mode 100644 index 0000000..4f9cef3 --- /dev/null +++ b/example_config/app_logos/logo.svg @@ -0,0 +1,63 @@ + + + +OPENGIS QWC2 MINIMAL diff --git a/config.json b/example_config/config.json similarity index 74% rename from config.json rename to example_config/config.json index 56d35dd..9910753 100644 --- a/config.json +++ b/example_config/config.json @@ -176,6 +176,50 @@ "position": 0 } }, + { + "name": "TopBar", + "cfg": { + "menuItems": [ + {"key": "ThemeSwitcher", "icon": "themes"}, + {"key": "LayerTree", "icon": "layers"}, + {"key": "LayerCatalog", "icon": "catalog"}, + {"key": "Share", "icon": "share"}, + {"key": "Bookmark", "icon": "bookmark"}, + {"key": "Tools", "icon": "tools", "subitems": [ + {"key": "Identify", "icon": "identify_region", "mode": "Region"}, + {"key": "TimeManager", "icon": "clock"}, + {"key": "Measure", "icon": "measure"}, + {"key": "Redlining", "icon": "draw"}, + {"key": "Editing", "icon": "editing"}, + {"key": "FeatureForm", "icon": "featureform"}, + {"key": "AttributeTable", "icon": "editing"}, + {"key": "DxfExport", "icon": "dxfexport"}, + {"key": "RasterExport", "icon": "rasterexport"} + ]}, + {"key": "Print", "icon": "print"}, + {"key": "Help", "icon": "info"}, + {"key": "ExternalLink", "icon": "link", "url": "http://example.com?extent=$e$", "target": "iframe"} + ], + "toolbarItems": [ + {"key": "Measure", "mode": "LineString", "icon": "measure_line"}, + {"key": "Measure", "mode": "Polygon", "icon": "measure_polygon"}, + {"key": "Print", "icon": "print"}, + {"key": "Identify", "icon": "identify_region", "mode": "Region"}, + {"key": "FeatureForm", "icon": "featureform"} + ], + "searchOptions": { + "minScaleDenom": 1000, + "showProviderSelection": true, + "providerSelectionAllowAll": true, + "zoomToLayers": false, + "showProvidersInPlaceholder": false + }, + "appMenuClearsTask": true, + "appMenuFilterField": true, + "appMenuVisibleOnStartup": false, + "logoUrl": "/" + } + }, { "name": "BottomBar", "cfg": { diff --git a/example_config/map_thumbs/bauprojekte.png b/example_config/map_thumbs/bauprojekte.png new file mode 100644 index 0000000..e46e57d Binary files /dev/null and b/example_config/map_thumbs/bauprojekte.png differ diff --git a/example_config/themes.json b/example_config/themes.json new file mode 100644 index 0000000..f88fccc --- /dev/null +++ b/example_config/themes.json @@ -0,0 +1,98 @@ +{ + "themes": { + "title": "root", + "subdirs": [], + "items": [], + "defaultScales": [ + 4000000, + 2000000, + 1000000, + 400000, + 200000, + 80000, + 40000, + 20000, + 10000, + 8000, + 6000, + 4000, + 2000, + 1000, + 500, + 250, + 100 + ], + "defaultPrintGrid": [ + { + "s": 10000, + "x": 1000, + "y": 1000 + }, + { + "s": 1000, + "x": 100, + "y": 100 + }, + { + "s": 100, + "x": 10, + "y": 10 + } + ], + "externalLayers": [], + "backgroundLayers": [ + { + "type": "wmts", + "url": "https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.leichte-basiskarte_reliefschattierung/default/current/2056/{TileMatrix}/{TileCol}/{TileRow}.jpeg", + "title": "SWISSTOPO: Leichte Basiskarte", + "name": "ch.swisstopo.leichte-basiskarte_reliefschattierung", + "tileMatrixPrefix": "", + "tileMatrixSet": "2056_26", + "originX": 2420000, + "originY": 1350000, + "projection:": "EPSG:2056", + "resolutions": [ + 4000, + 3750, + 3500, + 3250, + 3000, + 2750, + 2500, + 2250, + 2000, + 1750, + 1500, + 1250, + 1000, + 750, + 650, + 500, + 250, + 100, + 50, + 20, + 10, + 5, + 2.5, + 2, + 1.5, + 1, + 0.5, + 0.25 + ], + "tileSize": [ + 256, + 256 + ], + "attribution": { + "Title": { + "Title": "Hintergrundkarte © swisstopo" + } + }, + "thumbnail": "img/mapthumbs/default.jpg" + } + ], + "defaultWMSVersion": "1.3.0" + } +} \ No newline at end of file diff --git a/example_config/themesConfig.json b/example_config/themesConfig.json new file mode 100644 index 0000000..58a2438 --- /dev/null +++ b/example_config/themesConfig.json @@ -0,0 +1,91 @@ +{ + "themes":{ + "items":[], + "externalLayers":[], + "backgroundLayers":[{ + "type":"wmts", + "url":"https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.leichte-basiskarte_reliefschattierung/default/current/2056/{TileMatrix}/{TileCol}/{TileRow}.jpeg", + "title":"SWISSTOPO: Leichte Basiskarte", + "name":"ch.swisstopo.leichte-basiskarte_reliefschattierung", + "tileMatrixPrefix":"", + "tileMatrixSet":"2056_26", + "originX":2420000.0, + "originY":1350000.0, + "projection:":"EPSG:2056", + "resolutions":[ + 4000, + 3750, + 3500, + 3250, + 3000, + 2750, + 2500, + 2250, + 2000, + 1750, + 1500, + 1250, + 1000, + 750, + 650, + 500, + 250, + 100, + 50, + 20, + 10, + 5, + 2.5, + 2, + 1.5, + 1, + 0.5, + 0.25 + ], + "tileSize":[ + 256, + 256 + ], + "attribution":{ + "Title":"Hintergrundkarte © swisstopo" + } + }] + }, + "defaultScales":[ + 4000000, + 2000000, + 1000000, + 400000, + 200000, + 80000, + 40000, + 20000, + 10000, + 8000, + 6000, + 4000, + 2000, + 1000, + 500, + 250, + 100 + ], + "defaultPrintGrid":[ + { + "s":10000, + "x":1000, + "y":1000 + }, + { + "s":1000, + "x":100, + "y":100 + }, + { + "s":100, + "x":10, + "y":10 + } + ], + "defaultWMSVersion":"1.3.0" +} diff --git a/stats.html b/stats.html deleted file mode 100644 index e69de29..0000000 diff --git a/themesConfig.json b/themesConfig.json deleted file mode 100644 index 9b46c92..0000000 --- a/themesConfig.json +++ /dev/null @@ -1,390 +0,0 @@ -{ - "themes":{ - "items":[ - { - "url":"http://qwc2.sourcepole.ch/ows/uster/bauprojekte", - "attribution":"Stadt Uster", - "attributionUrl":"https://gis.uster.ch/", - "scales":[ - 80000, - 40000, - 20000, - 10000, - 8000, - 6000, - 4000, - 2000, - 1000, - 500, - 250, - 100 - ], - "printScales":[ - 80000, - 40000, - 20000, - 10000, - 8000, - 6000, - 4000, - 2000, - 1000, - 500, - 250, - 100 - ], - "printResolutions":[ - 150, - 300, - 600 - ], - "backgroundLayers":[ - { - "name":"mapnik", - "visibility":true - }, - { - "name":"opentopomap" - } - ], - "searchProviders":[ - "coordinates", - "uster", - { - "key": "nominatim", - "params": { - "countrycodes": "ch" - } - } - ], - "mapCrs":"EPSG:3857", - "additionalMouseCrs":[ - "EPSG:21781", - "EPSG:2056" - ] - }, - { - "title": "Demo", - "url": "http://qwc2.sourcepole.ch/ows/qwc_demo", - "default": true, - "attribution": "Demo attribution", - "attributionUrl": "https://127.0.0.1/", - "backgroundLayers": [ - { - "name": "bluemarble", - "printLayer": "bluemarble_bg", - "visibility": true - }, - { - "name": "mapnik", - "printLayer": "osm_bg" - } - ], - "searchProviders": [ - "coordinates", - "nominatim" - ], - "mapCrs": "EPSG:3857", - "additionalMouseCrs": [], - "extent": [-1000000, 4000000, 3000000, 8000000], - "skipEmptyFeatureAttributes": true, - "printResolutions": [300], - "thumbnail": "default.jpg" - } - ], - "externalLayers":[ - { - "name":"bauzonen", - "type":"wms", - "tiled": true, - "url":"https://wms.geo.admin.ch", - "params":{"LAYERS":"ch.are.bauzonen"}, - "infoFormats":["text/plain"] - } - ], - "backgroundLayers":[ - { - "name":"mapnik", - "title":"Open Street Map", - "type":"osm", - "source":"osm", - "thumbnail":"mapnik.jpg", - "attribution":"OpenStreetMap contributors", - "attributionUrl":"https://www.openstreetmap.org/copyright" - }, - { - "name":"Night2012", - "title":"NASAGIBS Night 2012", - "type":"tileprovider", - "source":"nasagibs", - "provider":"NASAGIBS.ViirsEarthAtNight2012", - "thumbnail":"Night2012.jpg", - "attribution":"Suomi NPP / VIIRS via NASA Earth Observatory" - }, - { - "name":"StamenWatercolor", - "title":"Stamen Watercolor/OSM", - "group":"stamen", - "type":"tileprovider", - "source":"stamen", - "provider":"Stamen.Watercolor" - }, - { - "name":"StamenToner", - "title":"Stamen Toner/OSM", - "group":"stamen", - "type":"tileprovider", - "source":"stamen", - "provider":"Stamen.Toner" - }, - { - "name":"group", - "title":"Group", - "type":"group", - "items":[ - { - "ref":"Gemeindegrenzen", - "minScale":10000, - "maxScale":300000 - }, - { - "ref":"oevhaltestellen" - } - ] - }, - { - "name":"Gemeindegrenzen", - "title":"Gemeindegrenzen", - "type":"wms", - "url":"http://wms.geo.admin.ch/", - "tiled":false, - "params":{ - "LAYERS":"ch.swisstopo.swissboundaries3d-gemeinde-flaeche.fill", - "STYLES":"default" - } - }, - { - "name":"oevhaltestellen", - "title":"Haltestellen öV", - "type":"wms", - "url":"http://wms.geo.admin.ch/", - "tiled": true, - "params": { - "LAYERS":"ch.bav.haltestellen-oev", - "STYLES":"default" - } - }, - { - "type":"wmts", - "url":"http://gibs.earthdata.nasa.gov/wmts/epsg3857/best/BlueMarble_ShadedRelief/default/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg", - "title":"Blue Marble", - "name":"bluemarble", - "tileMatrixPrefix":"", - "tileMatrixSet":"GoogleMapsCompatible_Level8", - "originX":-20037508.34278925, - "originY":20037508.34278925, - "projection:":"EPSG:3857", - "resolutions":[ - 156543.03390625, - 78271.516953125, - 39135.7584765625, - 19567.87923828125, - 9783.939619140625, - 4891.9698095703125, - 2445.9849047851562, - 1222.9924523925781 - ], - "tileSize":[ - 256, - 256 - ], - "thumbnail":"default.jpg" - }, - { - "type":"wms", - "url":"https://maps.omniscale.net/v2/swl-33d96aa8/style.default/map", - "title":"osm", - "name":"osm", - "srs":"EPSG:25832", - "params":{ - "LAYERS":"osm", - "STYLES":"", - "VERSION":"1.1.1" - }, - "thumbnail":"default2.jpg" - }, - { - "type":"tileprovider", - "provider":"OpenTopoMap", - "title":"OpenTopoMap", - "name":"opentopomap", - "thumbnail":"default.jpg", - "attribution":"Map data: © OpenStreetMap contributors, SRTM | Map style: © OpenTopoMap (CC-BY-SA)" - }, - { - "type":"wmts", - "url":"https://wxs.ign.fr/pratique/geoportail/wmts", - "title":"IGN", - "name":"GEOGRAPHICALGRIDSYSTEMS.MAPS", - "format":"image/jpeg", - "style":"normal", - "originX":-20037508, - "originY":20037508, - "projection":"EPSG:3857", - "resolutions":[ - 156543.03392804097, - 78271.51696402048, - 39135.75848201024, - 19567.87924100512, - 9783.93962050256, - 4891.96981025128, - 2445.98490512564, - 1222.99245256282, - 611.49622628141, - 305.748113140705, - 152.8740565703525, - 76.43702828517625, - 38.21851414258813, - 19.109257071294063, - 9.554628535647032, - 4.777314267823516, - 2.388657133911758, - 1.194328566955879 - ], - "tileSize":[ - 256, - 256 - ], - "requestEncoding":"KVP", - "tileMatrixSet":"PM", - "tileMatrixPrefix":"", - "thumbnail":"ign.png", - "attribution":"" - }, - { - "type":"group", - "title":"Karte farbig", - "name":"pixelkarte", - "items":[ - { - "ref":"av_grundbuchplan-color", - "minScale":0, - "maxScale":2000 - }, - { - "ref":"swisstopo.pixelkarte-farbe", - "minScale":2001 - } - ], - "thumbnail":"default.jpg" - }, - { - "type":"wmts", - "url":"https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.pixelkarte-farbe/default/current/2056/{TileMatrix}/{TileCol}/{TileRow}.jpeg", - "title":"swisstopo.pixelkarte-farbe", - "name":"swisstopo.pixelkarte-farbe", - "tileMatrixPrefix":"", - "tileMatrixSet":"2056_26", - "originX":2420000.0, - "originY":1350000.0, - "projection:":"EPSG:2056", - "resolutions":[ - 4000, - 3750, - 3500, - 3250, - 3000, - 2750, - 2500, - 2250, - 2000, - 1750, - 1500, - 1250, - 1000, - 750, - 650, - 500, - 250, - 100, - 50, - 20, - 10, - 5, - 2.5, - 2, - 1.5, - 1, - 0.5, - 0.25 - ], - "tileSize":[ - 256, - 256 - ], - "attribution":{ - "Title":"Hintergrundkarte © swisstopo" - } - }, - { - "type":"wms", - "name":"av_grundbuchplan-color", - "title":"av_grundbuchplan-color", - "url":"https://iap-map.gl.ch/ows/mainmap", - "params":{ - "LAYERS":"ch.gl.cadastre.av_grundbuchplan-color", - "STYLES":"default" - } - }, - { - "name":"luftbild_zh_2015", - "title":"Luftbild ZH 2015", - "type":"wms", - "url":"http://localhost/qgis/qgis_mapserv.fcgi", - "params":{ - "LAYERS":"Orthofoto_2015_stdzh_0.5_jpeg", - "STYLES":"default", - "VERSION":"1.3.0", - "MAP": "/home/sandro/wms/luftbild_zh.qgs" - } - } - ] - }, - "defaultScales":[ - 4000000, - 2000000, - 1000000, - 400000, - 200000, - 80000, - 40000, - 20000, - 10000, - 8000, - 6000, - 4000, - 2000, - 1000, - 500, - 250, - 100 - ], - "defaultPrintGrid":[ - { - "s":10000, - "x":1000, - "y":1000 - }, - { - "s":1000, - "x":100, - "y":100 - }, - { - "s":100, - "x":10, - "y":10 - } - ], - "defaultWMSVersion":"1.3.0" -} diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index fb57ccd..0000000 --- a/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - -