From 8ebfc0136b543913c93cfcd51804a3a6d170e44a Mon Sep 17 00:00:00 2001 From: Dan 'Ducky' Little Date: Mon, 9 Jan 2023 23:45:19 -0600 Subject: [PATCH 1/3] Add source projection information to map-sources Add a `src-proj` property to WFS sources to make configuring the projection information more explicit. --- src/gm3/actions/mapSource.js | 64 ++++++++++++++++------------ src/gm3/components/map/layers/wfs.js | 20 +++++---- src/gm3/query/wfs.js | 12 +++--- 3 files changed, 53 insertions(+), 43 deletions(-) diff --git a/src/gm3/actions/mapSource.js b/src/gm3/actions/mapSource.js index 47d9c3b8..19ec4923 100644 --- a/src/gm3/actions/mapSource.js +++ b/src/gm3/actions/mapSource.js @@ -36,7 +36,13 @@ import { } from "../components/map/layers/wfs"; import { EDIT_LAYER_NAME } from "../defaults"; -import * as util from "../util"; +import { + parseBoolean, + getTagContents, + getXmlTextContents, + getMapSourceName, + getLayerName, +} from "../util"; let MS_Z_INDEX = 100000; @@ -135,7 +141,7 @@ function parseProperties(msXml) { for (let x = 0, xx = options.length; x < xx; x++) { propDef.options.push({ value: options[x].getAttribute("value"), - label: util.getXmlTextContents(options[x]), + label: getXmlTextContents(options[x]), }); } } @@ -174,8 +180,8 @@ export const favoriteLayer = createAction( * @returns Object defining the map source */ function mapServerToDestType(msXml, conf, destType) { - let urls = util.getTagContents(msXml, "url", true); - const mapfile = util.getTagContents(msXml, "file", true)[0]; + let urls = getTagContents(msXml, "url", true); + const mapfile = getTagContents(msXml, "file", true)[0]; // if the url is null then default to the // mapserver url. @@ -220,11 +226,13 @@ function mapServerToWFS(msXml, conf) { /* MapServer has a pretty major bug, it will not properly * reproject bounding boxes for non-WGS84 layers when doing - * WFS queries. This tells the query engine in the map component, - * to use an internal work-around ... aka ... "hack". + * WFS queries. + * This forces using a query of projection of 4326 if it + * otherwise not set */ - wfsConf.wgs84Hack = true; - + if (!wfsConf.srcProj) { + wfsConf.srcProj = "EPSG:4326"; + } return wfsConf; } @@ -235,14 +243,14 @@ export function addFromXml(xml, config) { // initialize the map source object. const mapSource = { name: xml.getAttribute("name"), - urls: util.getTagContents(xml, "url", true), + urls: getTagContents(xml, "url", true), type: xml.getAttribute("type"), label: xml.getAttribute("title"), opacity: xml.getAttribute("opacity"), zIndex: xml.getAttribute("z-index"), - queryable: util.parseBoolean(xml.getAttribute("queryable"), true), + queryable: parseBoolean(xml.getAttribute("queryable"), true), // assume layers are printable - printable: util.parseBoolean(xml.getAttribute("printable"), true), + printable: parseBoolean(xml.getAttribute("printable"), true), refresh: null, layers: [], transforms: {}, @@ -250,6 +258,7 @@ export function addFromXml(xml, config) { config: {}, properties: [], idProperty: "_uuid", + srcProj: xml.getAttribute("src-proj"), }; // handle setting up the zIndex @@ -315,9 +324,9 @@ export function addFromXml(xml, config) { const layer = { name: layerXml.getAttribute("name"), - on: util.parseBoolean(layerXml.getAttribute("status")), - favorite: util.parseBoolean(layerXml.getAttribute("favorite")), - selectable: util.parseBoolean(layerXml.getAttribute("selectable")), + on: parseBoolean(layerXml.getAttribute("status")), + favorite: parseBoolean(layerXml.getAttribute("favorite")), + selectable: parseBoolean(layerXml.getAttribute("selectable")), label: layerTitle ? layerTitle : mapSource.label, templates: {}, legend: null, @@ -332,14 +341,14 @@ export function addFromXml(xml, config) { if (legends.length > 0) { layer.legend = { type: legends[0].getAttribute("type"), - contents: util.getXmlTextContents(legends[0]), + contents: getXmlTextContents(legends[0]), }; } // pull in an HTML attribution as available. const attribution = layerXml.getElementsByTagName("attribution"); if (attribution.length > 0) { - layer.attribution = util.getXmlTextContents(attribution[0]); + layer.attribution = getXmlTextContents(attribution[0]); } const templates = layerXml.getElementsByTagName("template"); @@ -351,7 +360,7 @@ export function addFromXml(xml, config) { const templateSrc = templateXml.getAttribute("src"); const templateAlias = templateXml.getAttribute("alias"); - const autoTemplate = util.parseBoolean(templateXml.getAttribute("auto")); + const autoTemplate = parseBoolean(templateXml.getAttribute("auto")); if (templateAlias) { templateDef = { type: "alias", @@ -369,17 +378,16 @@ export function addFromXml(xml, config) { } else { templateDef = { type: "local", - contents: util.getXmlTextContents(templateXml), + contents: getXmlTextContents(templateXml), }; } templateDef.highlight = - util.parseBoolean(templateXml.getAttribute("highlight"), true) !== - false; + parseBoolean(templateXml.getAttribute("highlight"), true) !== false; layer.templates[templateName] = templateDef; } // check to see if there are any style definitions - const style = util.getTagContents(layerXml, "style", false); + const style = getTagContents(layerXml, "style", false); if (style && style.length > 0) { // convert to JSON try { @@ -394,7 +402,7 @@ export function addFromXml(xml, config) { } // check to see if there are any filter definitions - const filter = util.getTagContents(layerXml, "filter", false); + const filter = getTagContents(layerXml, "filter", false); if (filter && filter.length > 0) { // convert to JSON try { @@ -526,8 +534,8 @@ export function getActiveMapSources(mapSources, onlyPrintable = false) { } export function getLayerFromPath(mapSources, path) { - const mapSourceName = util.getMapSourceName(path); - const layerName = util.getLayerName(path); + const mapSourceName = getMapSourceName(path); + const layerName = getLayerName(path); return getLayer(mapSources, { mapSourceName: mapSourceName, @@ -754,11 +762,11 @@ export function removeFeature(path, feature) { return (dispatch, getState) => { const mapSources = getState().mapSources; const layer = getLayerFromPath(mapSources, path); - const layerSrcName = util.getMapSourceName(path); + const layerSrcName = getMapSourceName(path); let mapSourceName = layerSrcName; if (layer && layer.queryAs && layer.queryAs.length > 0) { - mapSourceName = util.getMapSourceName(layer.queryAs[0]); + mapSourceName = getMapSourceName(layer.queryAs[0]); } const mapSource = getState().mapSources[mapSourceName]; @@ -945,11 +953,11 @@ export function saveFeature(path, feature) { return (dispatch, getState) => { const mapSources = getState().mapSources; const layer = getLayerFromPath(mapSources, path); - const layerSrcName = util.getMapSourceName(path); + const layerSrcName = getMapSourceName(path); let mapSourceName = layerSrcName; if (layer && layer.queryAs && layer.queryAs.length > 0) { - mapSourceName = util.getMapSourceName(layer.queryAs[0]); + mapSourceName = getMapSourceName(layer.queryAs[0]); } const mapSource = getState().mapSources[mapSourceName]; diff --git a/src/gm3/components/map/layers/wfs.js b/src/gm3/components/map/layers/wfs.js index 9c889144..9a2713f5 100644 --- a/src/gm3/components/map/layers/wfs.js +++ b/src/gm3/components/map/layers/wfs.js @@ -31,6 +31,10 @@ import * as proj from "ol/proj"; import { featureToJson, transformFeatures } from "../../../util"; +export function getQueryProjection(mapSource, mapProjection) { + return mapSource.config?.srs || mapSource.srcProj || mapProjection; +} + function chainFilters(operator, filters) { let chainedFilters = null; if (filters.length > 1) { @@ -99,13 +103,10 @@ export function buildWfsQuery( ) { const geomField = getGeometryName(mapSource); - // the internal storage mechanism requires features - // returned from the query be stored in 4326 and then - // reprojected on render. - let queryProjection = mapProjection; - if (mapSource.wgs84Hack) { - queryProjection = new proj.get("EPSG:4326"); - } + // The WFS source may be in a different projection than the map + const queryProjection = new proj.get( + getQueryProjection(mapSource, mapProjection) + ); const filters = []; if (query.selection && query.selection.length > 0) { @@ -169,6 +170,7 @@ export function wfsGetFeatures( mapProjection, outputFormat ); + const queryProjection = getQueryProjection(mapSource, mapProjection); // TODO: check for params and properly join to URL! @@ -181,6 +183,8 @@ export function wfsGetFeatures( const gmlFormat = new GML2Format(); let features = gmlFormat.readFeatures(response).map((feature) => { + feature.getGeometry().transform(queryProjection, "EPSG:4326"); + const jsonFeature = featureToJson(feature); jsonFeature.properties = { ...jsonFeature.properties, @@ -202,7 +206,7 @@ function wfsTransact(mapSource, mapProjection, inFeatures) { const options = { featurePrefix: typeParts[0], featureType: typeParts[1], - srsName: config.srs || "EPSG:3857", + srsName: getQueryProjection(mapSource, mapProjection), }; if (config["namespace-uri"]) { diff --git a/src/gm3/query/wfs.js b/src/gm3/query/wfs.js index c278dac7..632b8cd6 100644 --- a/src/gm3/query/wfs.js +++ b/src/gm3/query/wfs.js @@ -5,15 +5,13 @@ import GML2Format from "ol/format/GML2"; import { applyPixelTolerance } from "./util"; import { transformFeatures, formatUrlParameters } from "../util"; -import { buildWfsQuery } from "../components/map/layers/wfs"; +import { + buildWfsQuery, + getQueryProjection, +} from "../components/map/layers/wfs"; export const wfsGetFeatureQuery = (layer, mapState, mapSource, query) => { - // the internal storage mechanism requires features - // returned from the query be stored in 4326 and then - // reprojected on render. - const queryProjection = mapSource.wgs84Hack - ? "EPSG:4326" - : mapState.projection; + const queryProjection = getQueryProjection(mapSource, mapState.projection); // check for the outputFormat based on the params let outputFormat = "text/xml; subtype=gml/2.1.2"; From 394cd610936056119f550819f790ecde4fcc7a16 Mon Sep 17 00:00:00 2001 From: Dan 'Ducky' Little Date: Sun, 19 Feb 2023 10:41:54 -0600 Subject: [PATCH 2/3] more fixes for the WFS display layer. Ensure projections are set properly when given src-srs --- src/gm3/components/map/layers/vector.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/gm3/components/map/layers/vector.js b/src/gm3/components/map/layers/vector.js index 53835581..b42399c4 100644 --- a/src/gm3/components/map/layers/vector.js +++ b/src/gm3/components/map/layers/vector.js @@ -41,8 +41,10 @@ import { tile, bbox } from "ol/loadingstrategy"; import VectorSource from "ol/source/Vector"; import VectorLayer from "ol/layer/Vector"; import { createXYZ } from "ol/tilegrid"; +import { transformExtent } from "ol/proj"; import { getEditStyle } from "./edit"; import { EDIT_LAYER_NAME } from "../../../defaults"; +import { getQueryProjection } from "./wfs"; // WARNING! This is a monkey patch in order to // allow rendering labels outside of a polygon's @@ -76,9 +78,15 @@ function defineSource(mapSource) { format = GeoJSONFormat; } + // TODO: Ensure this gets the real map projection when + // the code supports alternative projections. + const mapProjection = "EPSG:3857"; + const queryProjection = getQueryProjection(mapSource, mapProjection); + return { - format: new format({}), - projection: "EPSG:4326", + format: new format({ + srsName: queryProjection, + }), url: function (extent) { if (typeof mapSource.params.typename === "undefined") { console.error( @@ -86,15 +94,21 @@ function defineSource(mapSource) { ); } + const queryExtent = transformExtent( + extent, + mapProjection, + queryProjection + ); + const urlParams = Object.assign( {}, { - srsname: "EPSG:3857", + srs: queryProjection, outputFormat: outputFormat, service: "WFS", version: "1.1.0", request: "GetFeature", - bbox: extent.concat("EPSG:3857").join(","), + bbox: queryExtent, }, mapSource.params ); From 7ab0e6b693849a58710f0e4472add0fdce01c887 Mon Sep 17 00:00:00 2001 From: Dan 'Ducky' Little Date: Sun, 19 Feb 2023 20:34:51 -0600 Subject: [PATCH 3/3] Allow skipping the use of bbox filter. --- src/gm3/components/map/layers/vector.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/gm3/components/map/layers/vector.js b/src/gm3/components/map/layers/vector.js index b42399c4..99547e6e 100644 --- a/src/gm3/components/map/layers/vector.js +++ b/src/gm3/components/map/layers/vector.js @@ -37,7 +37,7 @@ import { import GML2Format from "ol/format/GML2"; import GeoJSONFormat from "ol/format/GeoJSON"; import EsriJsonFormat from "ol/format/EsriJSON"; -import { tile, bbox } from "ol/loadingstrategy"; +import { tile, bbox, all } from "ol/loadingstrategy"; import VectorSource from "ol/source/Vector"; import VectorLayer from "ol/layer/Vector"; import { createXYZ } from "ol/tilegrid"; @@ -78,6 +78,12 @@ function defineSource(mapSource) { format = GeoJSONFormat; } + const strategyName = mapSource.config?.strategy; + let strategy = bbox; + if (strategyName === "all") { + strategy = all; + } + // TODO: Ensure this gets the real map projection when // the code supports alternative projections. const mapProjection = "EPSG:3857"; @@ -94,12 +100,6 @@ function defineSource(mapSource) { ); } - const queryExtent = transformExtent( - extent, - mapProjection, - queryProjection - ); - const urlParams = Object.assign( {}, { @@ -108,14 +108,21 @@ function defineSource(mapSource) { service: "WFS", version: "1.1.0", request: "GetFeature", - bbox: queryExtent, }, mapSource.params ); + if (strategy === bbox) { + const queryExtent = transformExtent( + extent, + mapProjection, + queryProjection + ); + urlParams.bbox = queryExtent; + } return joinUrl(mapSource.urls[0], urlParams); }, - strategy: bbox, + strategy, }; } else if (mapSource.type === "geojson") { return {