wfsUtils.js 11 KB

12345
  1. /*
  2. All material copyright ESRI, All Rights Reserved, unless otherwise specified.
  3. See https://js.arcgis.com/4.25/esri/copyright.txt for details.
  4. */
  5. import"../../geometry.js";import e from"../../request.js";import t from"../../core/Error.js";import{cache as r,find as n}from"../../core/iteratorUtils.js";import{isNone as a,isSome as o}from"../../core/maybe.js";import{eachAlways as s}from"../../core/promiseUtils.js";import{isHTTPSProtocol as i,hasSameOrigin as p,toHTTPS as u}from"../../core/urlUtils.js";import{initializeProjection as c,project as l}from"../../geometry/projection.js";import{WGS84 as m,equals as y}from"../../geometry/support/spatialReferenceUtils.js";import{featureGeometryTypeKebabDictionary as f}from"../../geometry/support/typeUtils.js";import{getGeometryType as d}from"../graphics/sources/geojson/geojson.js";import{parseDate as g}from"./dateUtils.js";import{visitXML as w,iterateXML as b}from"./xmlUtils.js";import h from"../support/Field.js";import T from"../../geometry/SpatialReference.js";import F from"../../geometry/Extent.js";const S="xlink:href",x="2.0.0",C="__esri_wfs_id__",E="wfs-layer:getWFSLayerTypeInfo-error",N="wfs-layer:empty-service",P="wfs-layer:feature-type-not-found",R="wfs-layer:geojson-not-supported",j="wfs-layer:kvp-encoding-not-supported",A="wfs-layer:malformed-json",G="wfs-layer:unknown-geometry-type",k="wfs-layer:unknown-field-type",v="wfs-layer:unsupported-spatial-reference",U="wfs-layer:unsupported-wfs-version";async function D(t,r){const n=I((await e(t,{responseType:"text",query:{SERVICE:"WFS",REQUEST:"GetCapabilities",VERSION:x,...r?.customParameters},signal:r?.signal})).data);return $(t,n),n}function I(e){const t=te(e);ne(t),ae(t);const n=t.firstElementChild,a=r(M(n));return{operations:L(n),get featureTypes(){return Array.from(a())},readFeatureTypes:a}}const O=new Set(["json","application/json","geojson","application/json; subtype=geojson"]);function L(e){let r=!1;const n={GetCapabilities:{url:""},DescribeFeatureType:{url:""},GetFeature:{url:"",outputFormat:null,supportsPagination:!1}};if(w(e,{OperationsMetadata:{Operation:e=>{switch(e.getAttribute("name")){case"GetCapabilities":return{DCP:{HTTP:{Get:e=>{n.GetCapabilities.url=e.getAttribute(S)}}}};case"DescribeFeatureType":return{DCP:{HTTP:{Get:e=>{n.DescribeFeatureType.url=e.getAttribute(S)}}}};case"GetFeature":return{DCP:{HTTP:{Get:e=>{n.GetFeature.url=e.getAttribute(S)}}},Parameter:e=>{if("outputFormat"===e.getAttribute("name"))return{AllowedValues:{Value:e=>{const t=e.textContent;t&&O.has(t.toLowerCase())&&(n.GetFeature.outputFormat=t)}}}}}}},Constraint:e=>{switch(e.getAttribute("name")){case"KVPEncoding":return{DefaultValue:e=>{r="true"===e.textContent.toLowerCase()}};case"ImplementsResultPaging":return{DefaultValue:e=>{n.GetFeature.supportsPagination="true"===e.textContent.toLowerCase()}}}}}}),!r)throw new t(j,"WFS service doesn't support key/value pair (KVP) encoding");if(a(n.GetFeature.outputFormat))throw new t(R,"WFS service doesn't support GeoJSON output format");return n}function $(e,t){i(e)&&(p(e,t.operations.DescribeFeatureType.url,!0)&&(t.operations.DescribeFeatureType.url=u(t.operations.DescribeFeatureType.url)),p(e,t.operations.GetFeature.url,!0)&&(t.operations.GetFeature.url=u(t.operations.GetFeature.url)))}function M(e){return b(e,{FeatureTypeList:{FeatureType:e=>{const t={typeName:"undefined:undefined",name:"",title:"",description:"",extent:null,namespacePrefix:"",namespaceUri:"",supportedSpatialReferences:[]},r=new Set([4326]),n=e=>{const t=parseInt(e.textContent?.match(/(?<wkid>\d+$)/i)?.groups?.wkid??"",10);Number.isNaN(t)||r.add(t)};return w(e,{Name:e=>{const{name:r,prefix:n}=re(e.textContent);t.typeName=`${n}:${r}`,t.name=r,t.namespacePrefix=n,t.namespaceUri=e.lookupNamespaceURI(n)},Abstract:e=>{t.description=e.textContent},Title:e=>{t.title=e.textContent},WGS84BoundingBox:e=>{t.extent=V(e)},DefaultSRS:n,DefaultCRS:n,OtherSRS:n,OtherCRS:n}),t.title||(t.title=t.name),t.supportedSpatialReferences.push(...r),t}}})}function V(e){let t,r,n,a;for(const o of e.children)switch(o.localName){case"LowerCorner":[t,r]=o.textContent.split(" ").map((e=>Number.parseFloat(e)));break;case"UpperCorner":[n,a]=o.textContent.split(" ").map((e=>Number.parseFloat(e)))}return{xmin:t,ymin:r,xmax:n,ymax:a,spatialReference:m}}function W(e,t,r){return n(e,(e=>r?e.name===t&&e.namespaceUri===r:e.typeName===t||e.name===t))}async function X(e,t,r,n={}){const{featureType:a,extent:o}=await Y(e,t,r,n),{fields:s,geometryType:i,swapXY:p,objectIdField:u,geometryField:c}=await q(e,a.typeName,n);return{url:e.operations.GetCapabilities.url,name:a.name,namespaceUri:a.namespaceUri,fields:s,geometryField:c,geometryType:i,objectIdField:u,spatialReference:n.spatialReference??T.WGS84,extent:o,swapXY:p,wfsCapabilities:e,customParameters:n.customParameters}}async function Y(e,r,n,o={}){const{spatialReference:s=T.WGS84}=o,i=e.readFeatureTypes(),p=r?W(i,r,n):i.next().value;if(a(p))throw r?new t(P,`The type '${r}' could not be found in the service`):new t(N,"The service is empty");let u=new F({...p.extent,spatialReference:s});if(!y(s,m))try{await c(m,s,void 0,o),u=l(u,m)}catch{throw new t(v,"Projection not supported")}return{extent:u,spatialReference:s,featureType:p}}async function q(e,r,n={}){const[o,i]=await s([J(e.operations.DescribeFeatureType.url,r,n),_(e,r,n)]);if(o.error||i.error)throw new t(E,`An error occurred while getting info about the feature type '${r}'`,{error:o.error||i.error});const{fields:p,errors:u}=o.value??{},c=o.value?.geometryType||i.value?.geometryType,l=i.value?.swapXY??!1;if(a(c))throw new t(G,`The geometry type could not be determined for type '${r}`,{typeName:r,geometryType:c,fields:p,errors:u});return{...z(p??[]),geometryType:c,swapXY:l}}function z(e){const t=e.find((e=>"geometry"===e.type));let r=e.find((e=>"oid"===e.type));return e=e.filter((e=>"geometry"!==e.type)),r||(r=new h({name:C,type:"oid",alias:C}),e.unshift(r)),{geometryField:t?.name??null,objectIdField:r.name,fields:e}}async function _(t,r,n={}){let a,o=!1;const[s,i]=await Promise.all([K(t.operations.GetFeature.url,r,t.operations.GetFeature.outputFormat,{...n,count:1}),e(t.operations.GetFeature.url,{responseType:"text",query:Z(r,void 0,{...n,count:1}),signal:n?.signal})]),p="FeatureCollection"===s.type&&s.features[0]?.geometry;if(p){let e;switch(a=f.fromJSON(d(p.type)),p.type){case"Point":e=p.coordinates;break;case"LineString":case"MultiPoint":e=p.coordinates[0];break;case"MultiLineString":case"Polygon":e=p.coordinates[0][0];break;case"MultiPolygon":e=p.coordinates[0][0][0]}const t=/<[^>]*pos[^>]*> *(-?\d+(?:\.\d+)?) (-?\d+(?:\.\d+)?)/.exec(i.data);if(t){const r=e[0].toFixed(3),n=e[1].toFixed(3),a=parseFloat(t[1]).toFixed(3);r===parseFloat(t[2]).toFixed(3)&&n===a&&(o=!0)}}return{geometryType:a,swapXY:o}}async function J(t,r,n){return Q(r,(await e(t,{responseType:"text",query:{SERVICE:"WFS",REQUEST:"DescribeFeatureType",VERSION:x,TYPENAME:r,...n?.customParameters},signal:n?.signal})).data)}function Q(e,r){const{name:a}=re(e),s=te(r);ae(s);const i=n(b(s.firstElementChild,{element:e=>({name:e.getAttribute("name"),typeName:re(e.getAttribute("type")).name})}),(({name:e})=>e===a));if(o(i)){const e=n(b(s.firstElementChild,{complexType:e=>e}),(e=>e.getAttribute("name")===i.typeName));if(o(e))return B(e)}throw new t(P,`Type '${e}' not found in document`,{document:(new XMLSerializer).serializeToString(s)})}const H=new Set(["objectid","fid"]);function B(e){const r=[],n=[];let a;const o=b(e,{complexContent:{extension:{sequence:{element:e=>e}}}});for(const s of o){const o=s.getAttribute("name");if(!o)continue;let i,p;if(s.hasAttribute("type")?i=re(s.getAttribute("type")).name:w(s,{simpleType:{restriction:e=>(i=re(e.getAttribute("base")).name,{maxLength:e=>{p=+e.getAttribute("value")}})}}),!i)continue;const u="true"===s.getAttribute("nillable");let c=!1;switch(i.toLowerCase()){case"integer":case"nonpositiveinteger":case"negativeinteger":case"long":case"int":case"short":case"byte":case"nonnegativeinteger":case"unsignedlong":case"unsignedint":case"unsignedshort":case"unsignedbyte":case"positiveinteger":n.push(new h({name:o,alias:o,type:"integer",nullable:u}));break;case"float":case"double":case"decimal":n.push(new h({name:o,alias:o,type:"double",nullable:u}));break;case"boolean":case"string":case"gyearmonth":case"gyear":case"gmonthday":case"gday":case"gmonth":case"anyuri":case"qname":case"notation":case"normalizedstring":case"token":case"language":case"idrefs":case"entities":case"nmtoken":case"nmtokens":case"name":case"ncname":case"id":case"idref":case"entity":case"duration":case"time":n.push(new h({name:o,alias:o,type:"string",nullable:u,length:p??255}));break;case"datetime":case"date":n.push(new h({name:o,alias:o,type:"date",nullable:u,length:p??36}));break;case"pointpropertytype":a="point",c=!0;break;case"multipointpropertytype":a="multipoint",c=!0;break;case"curvepropertytype":case"multicurvepropertytype":case"multilinestringpropertytype":a="polyline",c=!0;break;case"surfacepropertytype":case"multisurfacepropertytype":case"multipolygonpropertytype":a="polygon",c=!0;break;case"geometrypropertytype":case"multigeometrypropertytype":c=!0,r.push(new t(G,`geometry type '${i}' is not supported`,{type:(new XMLSerializer).serializeToString(e)}));break;default:r.push(new t(k,`Unknown field type '${i}'`,{type:(new XMLSerializer).serializeToString(e)}))}c&&n.push(new h({name:o,alias:o,type:"geometry",nullable:u}))}for(const t of n)if("integer"===t.type&&!t.nullable&&H.has(t.name.toLowerCase())){t.type="oid";break}return{geometryType:a,fields:n,errors:r}}async function K(r,n,a,o){let{data:s}=await e(r,{responseType:"text",query:Z(n,a,o),signal:o?.signal});s=s.replace(/": +(-?\d+),(\d+)(,)?/g,'": $1.$2$3');try{if(o?.dateFields?.length){const e=new Set(o.dateFields);return JSON.parse(s,((t,r)=>e.has(t)?g(r):r))}return JSON.parse(s)}catch(i){throw new t(A,"Error while parsing the response",{response:s,error:i})}}function Z(e,t,r){return{SERVICE:"WFS",REQUEST:"GetFeature",VERSION:x,TYPENAMES:e,OUTPUTFORMAT:t,SRSNAME:"EPSG:4326",STARTINDEX:r?.startIndex,COUNT:r?.count,...r?.customParameters}}async function ee(t,r,n){const a=te((await e(t,{responseType:"text",query:{SERVICE:"WFS",REQUEST:"GetFeature",VERSION:x,TYPENAMES:r,RESULTTYPE:"hits",...n?.customParameters},signal:n?.signal})).data);ae(a);const o=Number.parseFloat(a.firstElementChild.getAttribute("numberMatched"));return Number.isNaN(o)?0:o}function te(e){return(new DOMParser).parseFromString(e.trim(),"text/xml")}function re(e){const[t,r]=e.split(":");return{prefix:r?t:"",name:r??t}}function ne(e){const r=e.firstElementChild?.getAttribute("version");if(r&&r!==x)throw new t(U,`Unsupported WFS version ${r}. Supported version: ${x}`)}function ae(e){let r="",n="";if(w(e.firstElementChild,{Exception:e=>(r=e.getAttribute("exceptionCode"),{ExceptionText:e=>{n=e.textContent}})}),r)throw new t(`wfs-layer:${r}`,n)}export{C as WFS_OID_FIELD_NAME,J as describeFeatureType,W as findFeatureType,D as getCapabilities,K as getFeature,ee as getFeatureCount,Y as getFeatureTypeInfo,X as getWFSLayerInfo,Q as parseDescribeFeatureTypeResponse,I as parseGetCapabilitiesResponse,z as prepareWFSLayerFields};