From a27f9182b6d75db682d6cfd1c7d5d97faf6153c6 Mon Sep 17 00:00:00 2001 From: Michael Auer Date: Wed, 11 Oct 2023 18:15:00 +0200 Subject: [PATCH] fix (map-generation, ui): avoid selecting invalid bboxes - validate bbox and do not allow sending form if bbox crosses the antimeridian - notify user with warning and display antimeridian if it is in the selected bbox Refs: #279 --- client-src/base/base.css | 13 +++++-- client-src/create/form.js | 47 ++++++++++++++++++-------- client-src/create/index.js | 7 ++-- client-src/create/map.js | 46 ++++++++++++++++++++++++- client-src/create/messageController.js | 28 +++++++++++++++ sketch_map_tool/templates/create.html | 15 ++++++-- 6 files changed, 133 insertions(+), 23 deletions(-) create mode 100644 client-src/create/messageController.js diff --git a/client-src/base/base.css b/client-src/base/base.css index a66ca7d1..d0dadb3b 100644 --- a/client-src/base/base.css +++ b/client-src/base/base.css @@ -81,12 +81,15 @@ body { display: inline-block; } -.infobox { +.infobox, +.warningbox { position: relative; padding-left: 3.5rem; + margin: 0.5rem 0; } -.infobox::before { +.infobox::before, +.warningbox::before { content: "i"; position: absolute; width: 2rem; @@ -101,6 +104,12 @@ body { top: calc(50% - 1rem); } +.warningbox::before { + content: "!"; + background-color: var(--heigit-red); + color: white; +} + .github-icon { display: inline-block; position: relative; diff --git a/client-src/create/form.js b/client-src/create/form.js index c1d8bb8a..246fa167 100644 --- a/client-src/create/form.js +++ b/client-src/create/form.js @@ -1,11 +1,9 @@ -import { PAPER_FORMAT, ORIENTATION, Margin } from "@giscience/ol-print-layout-control"; -import { - toLonLat, get as getProjection, transformExtent, -} from "ol/proj"; +import { Margin, ORIENTATION, PAPER_FORMAT } from "@giscience/ol-print-layout-control"; +import { get as getProjection, toLonLat, transformExtent } from "ol/proj"; import { SKETCH_MAP_MARGINS } from "./sketchMapMargins"; -import { fillSelectOptions, setDisabled } from "../shared"; +import { fillSelectOptions } from "../shared"; -function bindFormToPrintLayoutControl(printLayoutControl) { +function bindFormToPrintLayoutControl(printLayoutControl, messageController) { const paperFormats = { ...PAPER_FORMAT }; delete paperFormats.BROADSHEET; @@ -81,15 +79,31 @@ function bindFormToPrintLayoutControl(printLayoutControl) { // disable form submit and display info if zoom is lower than 9 function handleZoomChange(zoom) { if (zoom < 9) { - setDisabled("next-button", true); - document.querySelector("#infobox") - .classList - .remove("invisible"); + messageController.addWarning("zoom-info"); } else { - setDisabled("next-button", false); - document.querySelector("#infobox") - .classList - .add("invisible"); + messageController.removeWarning("zoom-info"); + } + } + + function handleAntimeridian(bboxWgs84) { + // normalizeLon uses mathematic modulo like in R, not % symetric modulo like in Java + // or Javascript + const normalizeLon = (x) => ((((x + 180) % 360) + 360) % 360) - 180; + + const antimeridianLayer = printLayoutControl.getMap() + .getLayers().getArray() + .find((l) => l.get("name") === "Antimeridian"); + + if (!bboxWgs84) return; + // check if antimeridian is within extent -> when left (x1) is bigger than right (x2) + const [left, , right] = bboxWgs84; + + if (normalizeLon(left) > normalizeLon(right)) { + messageController.addWarning("antimeridian-info"); + antimeridianLayer.setVisible(true); + } else { + messageController.removeWarning("antimeridian-info"); + antimeridianLayer.setVisible(false); } } @@ -98,6 +112,7 @@ function bindFormToPrintLayoutControl(printLayoutControl) { .getView(); const initialZoom = view.getZoom(); handleZoomChange(initialZoom); + handleAntimeridian(printLayoutControl.getBboxAsLonLat()); // update form state on zoomchange printLayoutControl.getMap() @@ -114,10 +129,12 @@ function bindFormToPrintLayoutControl(printLayoutControl) { document.querySelector("#page-setup-form").submit(); }); - // update the URL when the selection is changed (e.g. to bookmark the current selection) printLayoutControl.on("change:bbox", (event) => { + // update the URL when the selection is changed (e.g. to bookmark the current selection) const newCenter = printLayoutControl.getMap().getView().getCenter(); window.history.replaceState({}, document.title, `?center=${newCenter}`); + // show warning and disable form if bbox crosses the antimeridian + handleAntimeridian(event.target.getBboxAsLonLat()); }); } diff --git a/client-src/create/index.js b/client-src/create/index.js index c386d7da..25d90fb7 100644 --- a/client-src/create/index.js +++ b/client-src/create/index.js @@ -4,8 +4,9 @@ import "@kirtandesai/ol-geocoder/dist/ol-geocoder.css"; import "./geocoder.css"; import "./create.css"; -import { createMap, addPrintLayoutControl, addGeocoderControl } from "./map.js"; +import { addGeocoderControl, addPrintLayoutControl, createMap } from "./map.js"; import { bindFormToPrintLayoutControl } from "./form.js"; +import { MessageController } from "./messageController"; // Retrieve potentially given map center from URL (e.g. from a bookmarked selection) const searchParams = new URLSearchParams(window.location.search); @@ -20,5 +21,7 @@ if (centerArg != null) { const map = createMap("map", center, 15); const printLayoutControl = addPrintLayoutControl(map); -bindFormToPrintLayoutControl(printLayoutControl); +const messageController = new MessageController(); + +bindFormToPrintLayoutControl(printLayoutControl, messageController); addGeocoderControl(map); diff --git a/client-src/create/map.js b/client-src/create/map.js index 6cfbfad4..c2407aa8 100644 --- a/client-src/create/map.js +++ b/client-src/create/map.js @@ -1,10 +1,53 @@ -import { Map, View } from "ol"; +import { Feature, Map, View } from "ol"; import { Tile } from "ol/layer"; import { OSM } from "ol/source"; import Geocoder from "@kirtandesai/ol-geocoder"; import { PrintLayout, PAPER_FORMAT, ORIENTATION } from "@giscience/ol-print-layout-control"; +import { fromLonLat } from "ol/proj"; +import { LineString } from "ol/geom"; +import VectorSource from "ol/source/Vector"; +import VectorLayer from "ol/layer/Vector"; +import { + Fill, Stroke, Style, Text, +} from "ol/style"; import { SKETCH_MAP_MARGINS } from "./sketchMapMargins.js"; +function createAntiMeridianLayer() { + // Create a LineString feature + const lineString = new LineString([ + fromLonLat([180, -90]), // Start point just to the east of the antimeridian + fromLonLat([180, 90]), // End point just to the west of the antimeridian + ]); + + // Create a vector source and add the LineString feature to it + const vectorSource = new VectorSource({ + features: [new Feature({ + geometry: lineString, + })], + }); + + // Create a vector layer and set the source + return new VectorLayer({ + name: "Antimeridian", + source: vectorSource, + visible: false, + style: new Style({ + stroke: new Stroke({ + color: "red", + width: 3, + }), + text: new Text({ + text: "Antimeridian", + placement: "line", + offsetY: -10, + fill: new Fill({ color: "red" }), + font: "20px sans-serif", + repeat: 200, + }), + }), + }); +} + /** * Creates an OpenLayers Map to an element * @param {string} [target=map] - a div id where the map will be rendered @@ -26,6 +69,7 @@ function createMap(target = "map", lonLat = [966253.1800856147, 6344703.99262965 new Tile({ source: new OSM(), }), + createAntiMeridianLayer(), ], }); diff --git a/client-src/create/messageController.js b/client-src/create/messageController.js new file mode 100644 index 00000000..6f987dae --- /dev/null +++ b/client-src/create/messageController.js @@ -0,0 +1,28 @@ +import { setDisabled } from "../shared"; + +export class MessageController { + constructor() { + this.activeMessageIds = new Set(); + } + + addWarning(elementId) { + if (this.activeMessageIds.has(elementId)) return; + + setDisabled("next-button", true); + document.querySelector(`#${elementId}`) + .classList + .remove("hidden"); + + this.activeMessageIds.add(elementId); + } + + removeWarning(elementId) { + this.activeMessageIds.delete(elementId); + document.querySelector(`#${elementId}`) + .classList + .add("hidden"); + if (this.activeMessageIds.size === 0) { + setDisabled("next-button", false); + } + } +} diff --git a/sketch_map_tool/templates/create.html b/sketch_map_tool/templates/create.html index dc3fa03e..2d0faf0b 100644 --- a/sketch_map_tool/templates/create.html +++ b/sketch_map_tool/templates/create.html @@ -26,9 +26,18 @@