@defra/interactive-map 0.0.10-alpha → 0.0.12-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/im-shell.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/button-definition.md +21 -3
- package/docs/api/panel-definition.md +10 -12
- package/docs/api.md +80 -7
- package/docs/demo.mdx +70 -0
- package/docs/index.md +0 -4
- package/docs/plugins/plugin-context.md +3 -3
- package/docs/plugins/plugin-descriptor.md +37 -0
- package/docs/plugins/plugin-manifest.md +1 -1
- package/docusaurus.config.cjs +55 -25
- package/package.json +18 -9
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/src/manifest.js +3 -3
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/src/events.js +4 -14
- package/plugins/beta/draw-ml/src/modes/createDrawMode.js +1 -3
- package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/src/manifest.js +3 -3
- package/plugins/beta/use-location/dist/esm/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/src/manifest.js +7 -7
- package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
- package/plugins/interact/src/InteractInit.jsx +28 -6
- package/plugins/interact/src/InteractInit.test.js +19 -5
- package/plugins/interact/src/events.js +17 -15
- package/plugins/interact/src/events.test.js +25 -16
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/dist/esm/im-search-plugin.js +1 -1
- package/plugins/search/dist/esm/index.js +1 -1
- package/plugins/search/dist/umd/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/index.js +1 -1
- package/plugins/search/src/Search.jsx +9 -3
- package/plugins/search/src/Search.test.jsx +26 -6
- package/plugins/search/src/components/Form/Form.jsx +35 -7
- package/plugins/search/src/components/Form/Form.module.scss +27 -0
- package/plugins/search/src/components/Form/Form.test.jsx +99 -2
- package/plugins/search/src/components/SubmitButton/SubmitButton.jsx +28 -0
- package/plugins/search/src/components/SubmitButton/SubmitButton.module.scss +8 -0
- package/plugins/search/src/components/SubmitButton/SubmitButton.test.jsx +33 -0
- package/plugins/search/src/datasets.js +15 -11
- package/plugins/search/src/datasets.test.js +17 -2
- package/plugins/search/src/events/fetchSuggestions.js +1 -1
- package/plugins/search/src/index.js +1 -1
- package/plugins/search/src/index.test.js +4 -4
- package/plugins/search/src/reducer.js +9 -4
- package/plugins/search/src/reducer.test.js +12 -7
- package/plugins/search/src/search.scss +5 -1
- package/plugins/search/src/utils/parseOsNamesResults.js +18 -2
- package/plugins/search/src/utils/parseOsNamesResults.test.js +33 -15
- package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
- package/providers/beta/esri/src/appEvents.js +8 -2
- package/providers/beta/esri/src/esriProvider.js +25 -17
- package/providers/beta/esri/src/mapEvents.js +41 -4
- package/providers/beta/esri/src/utils/coords.js +34 -1
- package/providers/beta/esri/src/utils/coords.test.js +126 -0
- package/providers/beta/esri/src/utils/spatial.js +47 -1
- package/providers/beta/esri/src/utils/spatial.test.js +55 -0
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/esm/index.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/index.js +1 -1
- package/providers/maplibre/src/appEvents.js +10 -1
- package/providers/maplibre/src/appEvents.test.js +13 -4
- package/providers/maplibre/src/index.js +5 -13
- package/providers/maplibre/src/index.test.js +34 -15
- package/providers/maplibre/src/mapEvents.js +9 -1
- package/providers/maplibre/src/maplibreProvider.js +25 -15
- package/providers/maplibre/src/maplibreProvider.test.js +28 -2
- package/providers/maplibre/src/utils/spatial.js +51 -0
- package/providers/maplibre/src/utils/spatial.test.js +47 -0
- package/src/App/components/Actions/Actions.module.scss +5 -4
- package/src/App/components/MapButton/MapButton.jsx +4 -16
- package/src/App/components/MapButton/MapButton.module.scss +12 -12
- package/src/App/components/MapButton/MapButton.test.jsx +0 -9
- package/src/App/components/Panel/Panel.jsx +6 -6
- package/src/App/components/Panel/Panel.test.jsx +14 -15
- package/src/App/components/Viewport/MapController.jsx +6 -1
- package/src/App/hooks/useLayoutMeasurements.js +1 -1
- package/src/App/hooks/useLayoutMeasurements.test.js +1 -1
- package/src/App/hooks/useMapProviderOverrides.js +21 -1
- package/src/App/hooks/useMapProviderOverrides.test.js +51 -2
- package/src/App/hooks/useMarkersAPI.js +5 -3
- package/src/App/hooks/useModalPanelBehaviour.js +19 -2
- package/src/App/hooks/useModalPanelBehaviour.test.js +84 -60
- package/src/App/hooks/useVisibleGeometry.js +100 -0
- package/src/App/hooks/useVisibleGeometry.test.js +331 -0
- package/src/App/layout/Layout.jsx +5 -5
- package/src/App/layout/layout.module.scss +2 -4
- package/src/App/registry/panelRegistry.js +1 -10
- package/src/App/registry/panelRegistry.test.js +6 -11
- package/src/App/renderer/HtmlElementHost.jsx +12 -3
- package/src/App/renderer/HtmlElementHost.test.jsx +89 -0
- package/src/App/renderer/mapButtons.js +128 -28
- package/src/App/renderer/mapButtons.test.js +119 -19
- package/src/App/renderer/pluginWrapper.js +3 -2
- package/src/App/renderer/slots.js +1 -1
- package/src/App/store/AppProvider.jsx +1 -0
- package/src/App/store/MapProvider.jsx +18 -5
- package/src/App/store/MapProvider.test.jsx +56 -1
- package/src/App/store/appActionsMap.js +17 -9
- package/src/App/store/appActionsMap.test.js +33 -7
- package/src/App/store/appDispatchMiddleware.js +19 -0
- package/src/App/store/appDispatchMiddleware.test.js +56 -0
- package/src/App/store/mapActionsMap.js +4 -7
- package/src/InteractiveMap/InteractiveMap.js +18 -0
- package/src/InteractiveMap/InteractiveMap.test.js +12 -0
- package/src/config/appConfig.js +17 -15
- package/src/config/events.js +41 -4
- package/src/config/getInitialOpenPanels.js +2 -2
- package/src/config/getInitialOpenPanels.test.js +7 -7
- package/src/types.js +22 -11
- package/src/utils/getValueForStyle.js +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"@babel/runtime/helpers/
|
|
1
|
+
import e from"@babel/runtime/helpers/defineProperty";import t from"@babel/runtime/helpers/objectWithoutProperties";import r from"@babel/runtime/helpers/asyncToGenerator";import n from"@arcgis/core/config.js";import o from"@arcgis/core/layers/support/TileInfo.js";import i from"@arcgis/core/Map.js";import a from"@arcgis/core/views/MapView.js";import s from"@arcgis/core/layers/VectorTileLayer.js";import m from"@arcgis/core/geometry/Point.js";import{when as l,once as u,watch as c}from"@arcgis/core/core/reactiveUtils.js";import p from"@arcgis/core/geometry/Extent.js";var v={animationDuration:200},y=["showKeyboardHelp","selectControl","moveLarge","nudgeMap","zoomLarge","nudgeZoom"];var h=(e,t)=>{var r=null,n=function(){for(var n=arguments.length,o=new Array(n),i=0;i<n;i++)o[i]=arguments[i];clearTimeout(r),r=setTimeout(()=>{e(...o)},t)};return n.cancel=()=>{r&&(clearTimeout(r),r=null)},n};function d(e){var{mapProvider:t,view:r,baseTileLayer:n,events:o,eventBus:i,getZoom:a,getCenter:s,getBounds:m,getResolution:p}=e,v=!1,y=[],d=[],f=e=>{var t=(()=>{if(v||!r||r.destroyed||!r.extent)return null;var{maxZoom:e,minZoom:t}=r.constraints;return{center:s(),bounds:m(),resolution:p(),zoom:a(),isAtMaxZoom:r.zoom+.01>=e,isAtMinZoom:r.zoom-.01<=t}})();t&&i.emit(e,t)};l(()=>n.loaded&&r.resolution>0,()=>f(o.MAP_LOADED)),u(()=>r.ready).then(()=>{v||i.emit(o.MAP_READY,{map:t.map,view:t.view,mapStyleId:t.mapStyleId,mapSize:t.mapSize,crs:t.crs})}),u(()=>r.stationary).then(()=>f(o.MAP_FIRST_IDLE));var g=h(()=>f(o.MAP_MOVE_END),500);d.push(g),y.push(c(()=>[r.interacting,r.animation],e=>{var[t,r]=e;(t||r)&&i.emit(o.MAP_MOVE_START),t||r||g()}));var x,w,b,M=(x=()=>f(o.MAP_MOVE),w=10,b=0,function(){var e=Date.now();e-b>=w&&(b=e,x(...arguments))});d.push(M),y.push(c(()=>r.zoom,M)),y.push(c(()=>r.extent,()=>i.emit(o.MAP_RENDER),{initial:!1}));var E=h(()=>f(o.MAP_DATA_CHANGE),500);d.push(E),y.push(c(()=>r.updating,e=>!e&&E()));var A=null,S=null;return y.push(r.on("pointer-down",e=>{A=e.x,S=e.y})),y.push(r.on("pointer-up",e=>{var n;if(0===e.button&&!(Math.hypot(e.x-A,e.y-S)>6)&&"active"!==(null===(n=t.sketchViewModel)||void 0===n?void 0:n.state)){var a={x:e.x,y:e.y},s=r.toMap(a);s&&i.emit(o.MAP_CLICK,{point:a,coords:[s.x,s.y]})}})),{remove(){v=!0,d.forEach(e=>e.cancel()),y.forEach(e=>e.remove())}}}var f=e=>e?new p({xmin:e[0],ymin:e[1],xmax:e[2],ymax:e[3],spatialReference:{wkid:27700}}):void 0,g=e=>e?new m({x:e[0],y:e[1],spatialReference:{wkid:27700}}):void 0,x=(e,t)=>{if(e)if("FeatureCollection"===e.type)e.features.forEach(e=>x(e,t));else if("Feature"===e.type)x(e.geometry,t);else if("GeometryCollection"===e.type)e.geometries.forEach(e=>x(e,t));else{var r=e=>{Array.isArray(e)&&("number"==typeof e[0]?t.push(e):e.forEach(r))};r(e.coordinates)}},w=e=>{var t=[];x(e,t);var r=t.map(e=>e[0]),n=t.map(e=>e[1]);return f([Math.min(...r),Math.min(...n),Math.max(...r),Math.max(...n)])},b=e=>{var t=1609.344,r=e/t;if(r<.5/t)return"".concat(Math.round(e),"m");if(r<10){var n=Number.parseFloat(r.toFixed(1)),o=1===n?"mile":"miles";return"".concat(n," ").concat(o)}var i=Math.round(r),a=1===i?"mile":"miles";return"".concat(i," ").concat(a)};var M={top:0,right:0,bottom:0,left:0};function E(){return(E=r(function*(e,t){if(!e||!t)return[];var r=e.map.layers.filter(e=>e instanceof s);return r.length?(yield e.hitTest(t,{include:r.toArray()})).results.map(e=>({layerId:e.layer.id,layerTitle:e.layer.title||e.layer.id,type:e.layer.type,geometry:e.graphic.geometry,symbol:e.graphic.symbol})):[]})).apply(this,arguments)}var A=["container","padding","mapStyle","mapSize","maxExtent"];function S(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function T(t){for(var r=1;r<arguments.length;r++){var n=null!=arguments[r]?arguments[r]:{};r%2?S(Object(n),!0).forEach(function(r){e(t,r,n[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(n)):S(Object(n)).forEach(function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(n,e))})}return t}class P{constructor(e){var{mapProviderConfig:t={},events:r,eventBus:n}=e;this.events=r,this.eventBus=n,this.capabilities={supportedShortcuts:y,supportsMapSizes:!1},Object.assign(this,t),this.mapEventHandles=[],this.appEventHandles=[]}initMap(e){var m=this;return r(function*(){var{container:r,padding:l,mapStyle:u,mapSize:c,maxExtent:p}=e;t(e,A),m.mapStyleId=null==u?void 0:u.id,m.mapSize=c;var{events:v,eventBus:y}=m;m.setupConfig&&(yield m.setupConfig(n));var h=new s({id:"baselayer",url:u.url,visible:!0}),x=new i({layers:[h]}),w=p?f(p):null,b=new a({spatialReference:27700,container:r,map:x,zoom:e.zoom,center:g(e.center),maxExtent:p,constraints:{lods:o.create({spatialReference:{wkid:27700}}).lods,snapToZoom:!1,minZoom:e.minZoom,maxZoom:e.maxZoom,maxScale:0,geometry:w,rotationEnabled:!1},ui:{components:[]},popupEnabled:!1});(e=>{if(e){var t=e.querySelector(".esri-view-surface");t.removeAttribute("role"),t.tabIndex=-1,t.style["outline-color"]="transparent",t.style.touchAction="none"}})(b.container),b.padding=l,e.bounds&&b.when(()=>b.goTo(f(e.bounds),{duration:0})),m.mapEventHandles=d({mapProvider:m,view:b,baseTileLayer:h,events:v,eventBus:y,getZoom:m.getZoom.bind(m),getCenter:m.getCenter.bind(m),getBounds:m.getBounds.bind(m),getResolution:m.getResolution.bind(m)}),m.appEventHandles=function(e){var{baseTileLayer:t,events:r,eventBus:n}=e,o=e=>{t.loadStyle(e.url).then(()=>{n.emit(r.MAP_STYLE_CHANGE,{mapStyleId:e.id})})};return n.on(r.MAP_SET_STYLE,o),{remove(){n.off(r.MAP_SET_STYLE,o)}}}({baseTileLayer:h,events:v,eventBus:y})||[],m.map=x,m.view=b,m.baseTileLayer=h})()}destroyMap(){var e,t;null===(e=this.mapEvents)||void 0===e||e.remove(),null===(t=this.appEvents)||void 0===t||t.remove(),this.mapEvents=null,this.appEvents=null,this.view&&(this.view.container=null,this.view.destroy(),this.view=null),this.map&&(this.map.removeAll(),this.map=null)}setView(e){var t,{center:r,zoom:n}=e;null===(t=this.view.animation)||void 0===t||t.destroy();var o={center:r?new m({x:r[0],y:r[1],spatialReference:{wkid:27700}}):this.view.center,zoom:null!=n?n:this.view.zoom};this.view.goTo(T(T({},o),{},{duration:v.animationDuration}))}zoomIn(e){var t;null===(t=this.view.animation)||void 0===t||t.destroy(),this.view.goTo({zoom:this.view.zoom+e,duration:v.animationDuration})}zoomOut(e){var t;null===(t=this.view.animation)||void 0===t||t.destroy(),this.view.goTo({zoom:this.view.zoom-e,duration:v.animationDuration})}panBy(e){var{x:t,y:r}=this.view.toScreen(this.view.center),n={x:t+e[0],y:r+e[1]},o=this.view.toMap(n);this.view.goTo({center:o,duration:v.animationDuration})}fitToBounds(e){var t=Array.isArray(e)?f(e):w(e);this.view.goTo(t,{duration:v.DELAY})}setPadding(e){this.view.padding=e}getCenter(){var e=this.view.center;return[e.x,e.y].map(e=>Math.round(100*e)/100)}getZoom(){return this.view.zoom}getBounds(){var{xmin:e,ymin:t,xmax:r,ymax:n}=this.view.extent;return[e,t,r,n].map(e=>Math.round(100*e)/100)}getFeaturesAtPoint(e,t){return function(e,t){return E.apply(this,arguments)}(this.view,e)}getAreaDimensions(){return(e=>{if(!(e&&e instanceof p))return"";var t=e.xmin,r=e.ymin,n=e.xmax,o=e.ymax-r,i=b(n-t),a=b(o);return"".concat(a," by ").concat(i)})(function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:M;if(!e.container)return null;var{width:r,height:n}=e.container.getBoundingClientRect(),o={x:t.left,y:n-t.bottom},i={x:r-t.right,y:t.top},a=e.toMap(o),s=e.toMap(i);return new p({xmin:a.x,ymin:a.y,xmax:s.x,ymax:s.y,spatialReference:a.spatialReference})}(this.view))}getCardinalMove(e,t){return function(e,t){var[r,n]=e,[o,i]=t,a=o-r,s=i-n,m=[];return Math.abs(s)>.1&&m.push("".concat(s>0?"north":"south"," ").concat(b(Math.abs(s)))),Math.abs(a)>.1&&m.push("".concat(a>0?"east":"west"," ").concat(b(Math.abs(a)))),m.join(", ")}(e,t)}getResolution(){return this.view.resolution}mapToScreen(e){var t=g(e),r=this.view.toScreen(t);return{x:r.x,y:r.y}}screenToMap(e){var t=this.view.toMap(e);return[t.x,t.y]}isGeometryObscured(e,t){return((e,t,r)=>{if(null==r||!r.container)return!1;var n=r.container.getBoundingClientRect(),o=w(e),i=[r.toScreen(new m({x:o.xmin,y:o.ymin,spatialReference:o.spatialReference})),r.toScreen(new m({x:o.xmin,y:o.ymax,spatialReference:o.spatialReference})),r.toScreen(new m({x:o.xmax,y:o.ymin,spatialReference:o.spatialReference})),r.toScreen(new m({x:o.xmax,y:o.ymax,spatialReference:o.spatialReference}))],a=Math.min(...i.map(e=>e.x)),s=Math.max(...i.map(e=>e.x)),l=Math.min(...i.map(e=>e.y)),u=Math.max(...i.map(e=>e.y)),c=t.left-n.left,p=t.top-n.top,v=t.right-n.left,y=t.bottom-n.top;return a<v&&s>c&&l<y&&u>p})(e,t,this.view)}}export{P as default};
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
export function attachAppEvents ({
|
|
1
|
+
export function attachAppEvents ({
|
|
2
|
+
baseTileLayer,
|
|
3
|
+
events,
|
|
4
|
+
eventBus
|
|
5
|
+
}) {
|
|
2
6
|
const handleSetMapStyle = mapStyle => {
|
|
3
7
|
baseTileLayer.loadStyle(mapStyle.url).then(() => {
|
|
4
|
-
eventBus.emit(events.MAP_STYLE_CHANGE,
|
|
8
|
+
eventBus.emit(events.MAP_STYLE_CHANGE, {
|
|
9
|
+
mapStyleId: mapStyle.id
|
|
10
|
+
})
|
|
5
11
|
})
|
|
6
12
|
}
|
|
7
13
|
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
// src/plugins/mapStyles/EsriProvider.jsx
|
|
2
2
|
import './esriProvider.scss'
|
|
3
3
|
import esriConfig from '@arcgis/core/config.js'
|
|
4
|
+
import TileInfo from '@arcgis/core/layers/support/TileInfo.js'
|
|
4
5
|
import EsriMap from '@arcgis/core/Map.js'
|
|
5
6
|
import MapView from '@arcgis/core/views/MapView.js'
|
|
6
7
|
import VectorTileLayer from '@arcgis/core/layers/VectorTileLayer.js'
|
|
8
|
+
import Point from '@arcgis/core/geometry/Point.js'
|
|
7
9
|
import { defaults, supportedShortcuts } from './defaults.js'
|
|
8
10
|
import { attachAppEvents } from './appEvents.js'
|
|
9
11
|
import { attachMapEvents } from './mapEvents.js'
|
|
10
|
-
import { getAreaDimensions, getCardinalMove, getPaddedExtent } from './utils/spatial.js'
|
|
12
|
+
import { getAreaDimensions, getCardinalMove, getPaddedExtent, isGeometryObscured } from './utils/spatial.js'
|
|
11
13
|
import { queryVectorTileFeatures } from './utils/query.js'
|
|
12
|
-
import { getExtentFromFlatCoords, getPointFromFlatCoords } from './utils/coords.js'
|
|
14
|
+
import { getExtentFromFlatCoords, getPointFromFlatCoords, getBboxFromGeoJSON } from './utils/coords.js'
|
|
13
15
|
import { cleanDOM } from './utils/esriFixes.js'
|
|
14
16
|
|
|
15
17
|
export default class EsriProvider {
|
|
@@ -28,7 +30,9 @@ export default class EsriProvider {
|
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
async initMap (config) {
|
|
31
|
-
const { container, padding, mapStyle, maxExtent, ...initConfig } = config
|
|
33
|
+
const { container, padding, mapStyle, mapSize, maxExtent, ...initConfig } = config
|
|
34
|
+
this.mapStyleId = mapStyle?.id
|
|
35
|
+
this.mapSize = mapSize
|
|
32
36
|
const { events, eventBus } = this
|
|
33
37
|
|
|
34
38
|
if (this.setupConfig) {
|
|
@@ -47,6 +51,7 @@ export default class EsriProvider {
|
|
|
47
51
|
center: getPointFromFlatCoords(config.center),
|
|
48
52
|
maxExtent: maxExtent,
|
|
49
53
|
constraints: {
|
|
54
|
+
lods: TileInfo.create({ spatialReference: { wkid: 27700 } }).lods,
|
|
50
55
|
snapToZoom: false,
|
|
51
56
|
minZoom: config.minZoom,
|
|
52
57
|
maxZoom: config.maxZoom,
|
|
@@ -112,24 +117,15 @@ export default class EsriProvider {
|
|
|
112
117
|
}
|
|
113
118
|
}
|
|
114
119
|
|
|
115
|
-
/** Returns the public API exposed via the map:ready event. */
|
|
116
|
-
getMapAPI () {
|
|
117
|
-
return {
|
|
118
|
-
map: this.map,
|
|
119
|
-
view: this.view,
|
|
120
|
-
crs: this.crs,
|
|
121
|
-
fitToBounds: this.fitToBounds.bind(this),
|
|
122
|
-
setView: this.setView.bind(this)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
120
|
// ==========================
|
|
127
121
|
// Side-effects
|
|
128
122
|
// ==========================
|
|
129
123
|
|
|
130
|
-
setView
|
|
124
|
+
setView({ center, zoom }) {
|
|
131
125
|
this.view.animation?.destroy()
|
|
132
|
-
|
|
126
|
+
const point = center ? new Point({ x: center[0], y: center[1], spatialReference: { wkid: 27700 }}) : this.view.center
|
|
127
|
+
const target = { center: point, zoom: zoom ?? this.view.zoom }
|
|
128
|
+
this.view.goTo({ ...target, duration: defaults.animationDuration })
|
|
133
129
|
}
|
|
134
130
|
|
|
135
131
|
zoomIn (zoomDelta) {
|
|
@@ -150,7 +146,8 @@ export default class EsriProvider {
|
|
|
150
146
|
}
|
|
151
147
|
|
|
152
148
|
fitToBounds (bounds) {
|
|
153
|
-
|
|
149
|
+
const extent = Array.isArray(bounds) ? getExtentFromFlatCoords(bounds) : getBboxFromGeoJSON(bounds)
|
|
150
|
+
this.view.goTo(extent, { duration: defaults.DELAY })
|
|
154
151
|
}
|
|
155
152
|
|
|
156
153
|
setPadding (padding) {
|
|
@@ -211,4 +208,15 @@ export default class EsriProvider {
|
|
|
211
208
|
const mapPoint = this.view.toMap(point)
|
|
212
209
|
return [mapPoint.x, mapPoint.y]
|
|
213
210
|
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Returns true if the geometry's screen bounding box overlaps the given panel rectangle.
|
|
214
|
+
*
|
|
215
|
+
* @param {object} geojson - GeoJSON Feature, FeatureCollection, or geometry.
|
|
216
|
+
* @param {DOMRect} panelRect - Bounding rect of the panel element (viewport coordinates).
|
|
217
|
+
* @returns {boolean}
|
|
218
|
+
*/
|
|
219
|
+
isGeometryObscured (geojson, panelRect) {
|
|
220
|
+
return isGeometryObscured(geojson, panelRect, this.view)
|
|
221
|
+
}
|
|
214
222
|
}
|
|
@@ -54,7 +54,13 @@ export function attachMapEvents ({
|
|
|
54
54
|
// ready
|
|
55
55
|
once(() => view.ready).then(() => {
|
|
56
56
|
if (!destroyed) {
|
|
57
|
-
eventBus.emit(events.MAP_READY,
|
|
57
|
+
eventBus.emit(events.MAP_READY, {
|
|
58
|
+
map: mapProvider.map,
|
|
59
|
+
view: mapProvider.view,
|
|
60
|
+
mapStyleId: mapProvider.mapStyleId,
|
|
61
|
+
mapSize: mapProvider.mapSize,
|
|
62
|
+
crs: mapProvider.crs
|
|
63
|
+
})
|
|
58
64
|
}
|
|
59
65
|
})
|
|
60
66
|
|
|
@@ -99,10 +105,41 @@ export function attachMapEvents ({
|
|
|
99
105
|
updating => !updating && emitDataChange()
|
|
100
106
|
))
|
|
101
107
|
|
|
102
|
-
//
|
|
103
|
-
handlers.push(view.on('click', e => {
|
|
104
|
-
|
|
108
|
+
// Click
|
|
109
|
+
// handlers.push(view.on('click', e => {
|
|
110
|
+
// const mapPoint = e.mapPoint
|
|
111
|
+
// const screenPoint = { x: e.x, y: e.y }
|
|
112
|
+
// eventBus.emit(events.MAP_CLICK, { point: screenPoint, coords: [mapPoint.x, mapPoint.y] })
|
|
113
|
+
// }))
|
|
114
|
+
|
|
115
|
+
// Using pointer-up instead of click/immediate-click — significantly more responsive as it
|
|
116
|
+
// bypasses ArcGIS's internal hit-testing pipeline. Track pointer-down position to suppress
|
|
117
|
+
// pointer-up after a drag/pan. Also suppress when SketchViewModel is active so draw/edit
|
|
118
|
+
// vertex clicks don't leak through to the map click handler.
|
|
119
|
+
const DRAG_TOLERANCE = 6
|
|
120
|
+
let pointerDownX = null
|
|
121
|
+
let pointerDownY = null
|
|
122
|
+
|
|
123
|
+
handlers.push(view.on('pointer-down', e => {
|
|
124
|
+
pointerDownX = e.x
|
|
125
|
+
pointerDownY = e.y
|
|
126
|
+
}))
|
|
127
|
+
|
|
128
|
+
handlers.push(view.on('pointer-up', e => {
|
|
129
|
+
if (e.button !== 0) {
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
if (Math.hypot(e.x - pointerDownX, e.y - pointerDownY) > DRAG_TOLERANCE) {
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
if (mapProvider.sketchViewModel?.state === 'active') {
|
|
136
|
+
return
|
|
137
|
+
}
|
|
105
138
|
const screenPoint = { x: e.x, y: e.y }
|
|
139
|
+
const mapPoint = view.toMap(screenPoint)
|
|
140
|
+
if (!mapPoint) {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
106
143
|
eventBus.emit(events.MAP_CLICK, { point: screenPoint, coords: [mapPoint.x, mapPoint.y] })
|
|
107
144
|
}))
|
|
108
145
|
|
|
@@ -23,7 +23,40 @@ const getPointFromFlatCoords = (coords) => {
|
|
|
23
23
|
: undefined
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
const collectCoords = (obj, acc) => {
|
|
27
|
+
if (!obj) return
|
|
28
|
+
if (obj.type === 'FeatureCollection') {
|
|
29
|
+
obj.features.forEach(f => collectCoords(f, acc))
|
|
30
|
+
} else if (obj.type === 'Feature') {
|
|
31
|
+
collectCoords(obj.geometry, acc)
|
|
32
|
+
} else if (obj.type === 'GeometryCollection') {
|
|
33
|
+
obj.geometries.forEach(g => collectCoords(g, acc))
|
|
34
|
+
} else {
|
|
35
|
+
const flatten = (coords) => {
|
|
36
|
+
if (!Array.isArray(coords)) { return }
|
|
37
|
+
if (typeof coords[0] === 'number') acc.push(coords)
|
|
38
|
+
else coords.forEach(flatten)
|
|
39
|
+
}
|
|
40
|
+
flatten(obj.coordinates)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get an ESRI Extent from any GeoJSON object (Feature, FeatureCollection, or geometry).
|
|
46
|
+
*
|
|
47
|
+
* @param {object} geojson - GeoJSON Feature, FeatureCollection, or geometry
|
|
48
|
+
* @returns {import('@arcgis/core/geometry/Extent.js').default}
|
|
49
|
+
*/
|
|
50
|
+
const getBboxFromGeoJSON = (geojson) => {
|
|
51
|
+
const points = []
|
|
52
|
+
collectCoords(geojson, points)
|
|
53
|
+
const xs = points.map(p => p[0])
|
|
54
|
+
const ys = points.map(p => p[1])
|
|
55
|
+
return getExtentFromFlatCoords([Math.min(...xs), Math.min(...ys), Math.max(...xs), Math.max(...ys)])
|
|
56
|
+
}
|
|
57
|
+
|
|
26
58
|
export {
|
|
27
59
|
getExtentFromFlatCoords,
|
|
28
|
-
getPointFromFlatCoords
|
|
60
|
+
getPointFromFlatCoords,
|
|
61
|
+
getBboxFromGeoJSON
|
|
29
62
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { getExtentFromFlatCoords, getPointFromFlatCoords, getBboxFromGeoJSON } from './coords.js'
|
|
2
|
+
|
|
3
|
+
jest.mock('@arcgis/core/geometry/Extent.js', () =>
|
|
4
|
+
jest.fn().mockImplementation((opts) => ({ ...opts, type: 'extent' }))
|
|
5
|
+
)
|
|
6
|
+
jest.mock('@arcgis/core/geometry/Point.js', () =>
|
|
7
|
+
jest.fn().mockImplementation((opts) => ({ ...opts, type: 'point' }))
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
describe('coords utils', () => {
|
|
11
|
+
describe('getExtentFromFlatCoords', () => {
|
|
12
|
+
test('returns an Extent with correct xmin/ymin/xmax/ymax', () => {
|
|
13
|
+
const result = getExtentFromFlatCoords([1, 2, 3, 4])
|
|
14
|
+
expect(result.xmin).toBe(1)
|
|
15
|
+
expect(result.ymin).toBe(2)
|
|
16
|
+
expect(result.xmax).toBe(3)
|
|
17
|
+
expect(result.ymax).toBe(4)
|
|
18
|
+
expect(result.spatialReference).toEqual({ wkid: 27700 })
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('returns undefined for falsy input', () => {
|
|
22
|
+
expect(getExtentFromFlatCoords(null)).toBeUndefined()
|
|
23
|
+
expect(getExtentFromFlatCoords(undefined)).toBeUndefined()
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('getPointFromFlatCoords', () => {
|
|
28
|
+
test('returns a Point with correct x/y', () => {
|
|
29
|
+
const result = getPointFromFlatCoords([100, 200])
|
|
30
|
+
expect(result.x).toBe(100)
|
|
31
|
+
expect(result.y).toBe(200)
|
|
32
|
+
expect(result.spatialReference).toEqual({ wkid: 27700 })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('returns undefined for falsy input', () => {
|
|
36
|
+
expect(getPointFromFlatCoords(null)).toBeUndefined()
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('getBboxFromGeoJSON', () => {
|
|
41
|
+
test('Point feature → extent wrapping that single point', () => {
|
|
42
|
+
const feature = {
|
|
43
|
+
type: 'Feature',
|
|
44
|
+
geometry: { type: 'Point', coordinates: [300000, 100000] },
|
|
45
|
+
properties: {}
|
|
46
|
+
}
|
|
47
|
+
const result = getBboxFromGeoJSON(feature)
|
|
48
|
+
expect(result.xmin).toBe(300000)
|
|
49
|
+
expect(result.ymin).toBe(100000)
|
|
50
|
+
expect(result.xmax).toBe(300000)
|
|
51
|
+
expect(result.ymax).toBe(100000)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('LineString feature → tight bbox around all coords', () => {
|
|
55
|
+
const feature = {
|
|
56
|
+
type: 'Feature',
|
|
57
|
+
geometry: {
|
|
58
|
+
type: 'LineString',
|
|
59
|
+
coordinates: [[100, 200], [300, 50], [500, 400]]
|
|
60
|
+
},
|
|
61
|
+
properties: {}
|
|
62
|
+
}
|
|
63
|
+
const result = getBboxFromGeoJSON(feature)
|
|
64
|
+
expect(result.xmin).toBe(100)
|
|
65
|
+
expect(result.ymin).toBe(50)
|
|
66
|
+
expect(result.xmax).toBe(500)
|
|
67
|
+
expect(result.ymax).toBe(400)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('Polygon feature → bbox around all rings', () => {
|
|
71
|
+
const feature = {
|
|
72
|
+
type: 'Feature',
|
|
73
|
+
geometry: {
|
|
74
|
+
type: 'Polygon',
|
|
75
|
+
coordinates: [[[0, 0], [10, 0], [10, 5], [0, 5], [0, 0]]]
|
|
76
|
+
},
|
|
77
|
+
properties: {}
|
|
78
|
+
}
|
|
79
|
+
const result = getBboxFromGeoJSON(feature)
|
|
80
|
+
expect(result.xmin).toBe(0)
|
|
81
|
+
expect(result.ymin).toBe(0)
|
|
82
|
+
expect(result.xmax).toBe(10)
|
|
83
|
+
expect(result.ymax).toBe(5)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('FeatureCollection → bbox spanning all features', () => {
|
|
87
|
+
const fc = {
|
|
88
|
+
type: 'FeatureCollection',
|
|
89
|
+
features: [
|
|
90
|
+
{ type: 'Feature', geometry: { type: 'Point', coordinates: [100, 200] }, properties: {} },
|
|
91
|
+
{ type: 'Feature', geometry: { type: 'Point', coordinates: [500, 800] }, properties: {} }
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
const result = getBboxFromGeoJSON(fc)
|
|
95
|
+
expect(result.xmin).toBe(100)
|
|
96
|
+
expect(result.ymin).toBe(200)
|
|
97
|
+
expect(result.xmax).toBe(500)
|
|
98
|
+
expect(result.ymax).toBe(800)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('GeometryCollection → bbox spanning all geometries', () => {
|
|
102
|
+
const gc = {
|
|
103
|
+
type: 'GeometryCollection',
|
|
104
|
+
geometries: [
|
|
105
|
+
{ type: 'Point', coordinates: [10, 20] },
|
|
106
|
+
{ type: 'LineString', coordinates: [[50, 5], [100, 90]] }
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
const result = getBboxFromGeoJSON(gc)
|
|
110
|
+
expect(result.xmin).toBe(10)
|
|
111
|
+
expect(result.ymin).toBe(5)
|
|
112
|
+
expect(result.xmax).toBe(100)
|
|
113
|
+
expect(result.ymax).toBe(90)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('result has correct spatialReference from getExtentFromFlatCoords', () => {
|
|
117
|
+
const feature = {
|
|
118
|
+
type: 'Feature',
|
|
119
|
+
geometry: { type: 'Point', coordinates: [1, 2] },
|
|
120
|
+
properties: {}
|
|
121
|
+
}
|
|
122
|
+
const result = getBboxFromGeoJSON(feature)
|
|
123
|
+
expect(result.spatialReference).toEqual({ wkid: 27700 })
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
})
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import Extent from '@arcgis/core/geometry/Extent.js'
|
|
2
|
+
import Point from '@arcgis/core/geometry/Point.js'
|
|
3
|
+
import { getBboxFromGeoJSON } from './coords.js'
|
|
2
4
|
|
|
3
5
|
// -----------------------------------------------------------------------------
|
|
4
6
|
// Internal (not exported)
|
|
@@ -124,8 +126,52 @@ const getPaddedExtent = (view, padding = DEFAULT_PADDING) => {
|
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Returns true if the geometry's screen bounding box overlaps the given panel rectangle.
|
|
131
|
+
* Used to decide whether to pan/zoom when a panel opens over a visibleGeometry target.
|
|
132
|
+
*
|
|
133
|
+
* @param {object} geojson - GeoJSON Feature, FeatureCollection, or geometry
|
|
134
|
+
* @param {DOMRect} panelRect - Bounding rect of the panel element (viewport coordinates)
|
|
135
|
+
* @param {import('@arcgis/core/views/MapView.js').default} view - ESRI MapView instance
|
|
136
|
+
* @returns {boolean}
|
|
137
|
+
*/
|
|
138
|
+
const isGeometryObscured = (geojson, panelRect, view) => {
|
|
139
|
+
if (!view?.container) {
|
|
140
|
+
return false
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const containerRect = view.container.getBoundingClientRect()
|
|
144
|
+
const extent = getBboxFromGeoJSON(geojson)
|
|
145
|
+
|
|
146
|
+
const corners = [
|
|
147
|
+
view.toScreen(new Point({ x: extent.xmin, y: extent.ymin, spatialReference: extent.spatialReference })),
|
|
148
|
+
view.toScreen(new Point({ x: extent.xmin, y: extent.ymax, spatialReference: extent.spatialReference })),
|
|
149
|
+
view.toScreen(new Point({ x: extent.xmax, y: extent.ymin, spatialReference: extent.spatialReference })),
|
|
150
|
+
view.toScreen(new Point({ x: extent.xmax, y: extent.ymax, spatialReference: extent.spatialReference }))
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
const screenMinX = Math.min(...corners.map(c => c.x))
|
|
154
|
+
const screenMaxX = Math.max(...corners.map(c => c.x))
|
|
155
|
+
const screenMinY = Math.min(...corners.map(c => c.y))
|
|
156
|
+
const screenMaxY = Math.max(...corners.map(c => c.y))
|
|
157
|
+
|
|
158
|
+
// Convert panelRect from viewport coords to view-container-relative coords
|
|
159
|
+
const panelLeft = panelRect.left - containerRect.left
|
|
160
|
+
const panelTop = panelRect.top - containerRect.top
|
|
161
|
+
const panelRight = panelRect.right - containerRect.left
|
|
162
|
+
const panelBottom = panelRect.bottom - containerRect.top
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
screenMinX < panelRight &&
|
|
166
|
+
screenMaxX > panelLeft &&
|
|
167
|
+
screenMinY < panelBottom &&
|
|
168
|
+
screenMaxY > panelTop
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
127
172
|
export {
|
|
128
173
|
getAreaDimensions,
|
|
129
174
|
getCardinalMove,
|
|
130
|
-
getPaddedExtent
|
|
175
|
+
getPaddedExtent,
|
|
176
|
+
isGeometryObscured
|
|
131
177
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { isGeometryObscured } from './spatial.js'
|
|
2
|
+
|
|
3
|
+
jest.mock('@arcgis/core/geometry/Extent.js', () =>
|
|
4
|
+
jest.fn().mockImplementation((opts) => ({ ...opts, type: 'extent' }))
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
jest.mock('@arcgis/core/geometry/Point.js', () =>
|
|
8
|
+
jest.fn().mockImplementation((opts) => ({ ...opts, type: 'point' }))
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
jest.mock('./coords.js', () => ({
|
|
12
|
+
getBboxFromGeoJSON: jest.fn(() => ({
|
|
13
|
+
xmin: 100, ymin: 200, xmax: 500, ymax: 600,
|
|
14
|
+
spatialReference: { wkid: 27700 },
|
|
15
|
+
type: 'extent'
|
|
16
|
+
}))
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
describe('isGeometryObscured', () => {
|
|
20
|
+
const geojson = { type: 'Feature', geometry: { type: 'Point', coordinates: [300, 400] }, properties: {} }
|
|
21
|
+
|
|
22
|
+
// Container sits at viewport origin so container-relative coords equal viewport coords
|
|
23
|
+
const makeView = (toScreenFn) => ({
|
|
24
|
+
container: {
|
|
25
|
+
getBoundingClientRect: jest.fn(() => ({ left: 0, top: 0, right: 1000, bottom: 800 }))
|
|
26
|
+
},
|
|
27
|
+
toScreen: jest.fn(toScreenFn)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Panel occupies the right 400px of the viewport
|
|
31
|
+
const panelRect = { left: 600, top: 0, right: 1000, bottom: 800, width: 400, height: 800 }
|
|
32
|
+
|
|
33
|
+
test('returns false when view has no container', () => {
|
|
34
|
+
expect(isGeometryObscured(geojson, panelRect, null)).toBe(false)
|
|
35
|
+
expect(isGeometryObscured(geojson, panelRect, {})).toBe(false)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('returns true when geometry screen bbox overlaps the panel rect', () => {
|
|
39
|
+
// Corners project into the panel (x: 650 is between panelLeft 600 and panelRight 1000)
|
|
40
|
+
const view = makeView(() => ({ x: 650, y: 400 }))
|
|
41
|
+
expect(isGeometryObscured(geojson, panelRect, view)).toBe(true)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('returns false when geometry screen bbox does not overlap the panel rect', () => {
|
|
45
|
+
// Corners project to x: 300, entirely left of panelLeft (600)
|
|
46
|
+
const view = makeView(() => ({ x: 300, y: 400 }))
|
|
47
|
+
expect(isGeometryObscured(geojson, panelRect, view)).toBe(false)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('projects all four bbox corners', () => {
|
|
51
|
+
const view = makeView(() => ({ x: 300, y: 400 }))
|
|
52
|
+
isGeometryObscured(geojson, panelRect, view)
|
|
53
|
+
expect(view.toScreen).toHaveBeenCalledTimes(4)
|
|
54
|
+
})
|
|
55
|
+
})
|