raintee-maputils 1.0.19 → 1.0.21

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.
@@ -0,0 +1,10 @@
1
+ .mapbox-ctrl-ruler button {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ color: #333;
6
+ }
7
+
8
+ .mapbox-ctrl-ruler button.-active {
9
+ color: #4264fb;
10
+ }
package/dist/index.js CHANGED
@@ -826,6 +826,24 @@ var RainteeConstants = /*#__PURE__*/Object.freeze({
826
826
  });
827
827
 
828
828
  // index.ts
829
+ var earthRadius = 63710088e-1;
830
+ var factors = {
831
+ centimeters: earthRadius * 100,
832
+ centimetres: earthRadius * 100,
833
+ degrees: 360 / (2 * Math.PI),
834
+ feet: earthRadius * 3.28084,
835
+ inches: earthRadius * 39.37,
836
+ kilometers: earthRadius / 1e3,
837
+ kilometres: earthRadius / 1e3,
838
+ meters: earthRadius,
839
+ metres: earthRadius,
840
+ miles: earthRadius / 1609.344,
841
+ millimeters: earthRadius * 1e3,
842
+ millimetres: earthRadius * 1e3,
843
+ nauticalmiles: earthRadius / 1852,
844
+ radians: 1,
845
+ yards: earthRadius * 1.0936
846
+ };
829
847
  function feature(geom, properties, options = {}) {
830
848
  const feat = { type: "Feature" };
831
849
  if (options.id === 0 || options.id) {
@@ -878,6 +896,36 @@ function multiPolygon(coordinates, properties, options = {}) {
878
896
  };
879
897
  return feature(geom, properties, options);
880
898
  }
899
+ function radiansToLength(radians, units = "kilometers") {
900
+ const factor = factors[units];
901
+ if (!factor) {
902
+ throw new Error(units + " units is invalid");
903
+ }
904
+ return radians * factor;
905
+ }
906
+ function degreesToRadians(degrees) {
907
+ const normalisedDegrees = degrees % 360;
908
+ return normalisedDegrees * Math.PI / 180;
909
+ }
910
+
911
+ // index.ts
912
+ function getCoord(coord) {
913
+ if (!coord) {
914
+ throw new Error("coord is required");
915
+ }
916
+ if (!Array.isArray(coord)) {
917
+ if (coord.type === "Feature" && coord.geometry !== null && coord.geometry.type === "Point") {
918
+ return [...coord.geometry.coordinates];
919
+ }
920
+ if (coord.type === "Point") {
921
+ return [...coord.coordinates];
922
+ }
923
+ }
924
+ if (Array.isArray(coord) && coord.length >= 2 && !Array.isArray(coord[0]) && !Array.isArray(coord[1])) {
925
+ return [...coord];
926
+ }
927
+ throw new Error("coord must be GeoJSON Point or an Array of numbers");
928
+ }
881
929
 
882
930
  // index.js
883
931
  function coordEach(geojson, callback, excludeWrapCoord) {
@@ -5818,6 +5866,457 @@ var RainteeSourceMapTool = /*#__PURE__*/Object.freeze({
5818
5866
  treeDataAdapterNext: treeDataAdapterNext
5819
5867
  });
5820
5868
 
5869
+ /**
5870
+ * Create mapbox control container
5871
+ * @param {string} className
5872
+ */
5873
+ function controlContainer(className) {
5874
+ const container = document.createElement('div');
5875
+ container.classList.add('mapboxgl-ctrl', 'mapboxgl-ctrl-group', className);
5876
+ return container;
5877
+ }
5878
+
5879
+ /**
5880
+ * Create mapbox control button
5881
+ * @param {Object} options
5882
+ * @param {string=} options.title
5883
+ * @param {Node=} options.icon
5884
+ * @param {string=} options.textContent
5885
+ * @param {boolean=} options.disabled
5886
+ * @param {boolean=} options.hidden
5887
+ * @param {string=} options.className
5888
+ * @param {() => void=} options.onClick
5889
+ */
5890
+ function controlButton(options = {}) {
5891
+ const button = document.createElement('button');
5892
+ button.type = 'button';
5893
+ if (options.title) {
5894
+ button.title = options.title;
5895
+ }
5896
+ if (options.icon) {
5897
+ button.appendChild(options.icon);
5898
+ }
5899
+ if (options.textContent) {
5900
+ button.textContent = options.textContent;
5901
+ }
5902
+ if (options.disabled) {
5903
+ button.disabled = true;
5904
+ }
5905
+ if (options.hidden) {
5906
+ button.hidden = true;
5907
+ }
5908
+ if (options.className) {
5909
+ button.classList.add(options.className);
5910
+ }
5911
+ if (options.onClick) {
5912
+ button.addEventListener('click', () => {
5913
+ if (!options.onClick) return;
5914
+ options.onClick();
5915
+ });
5916
+ }
5917
+ return button;
5918
+ }
5919
+
5920
+ /**
5921
+ * Create SVG element from string code
5922
+ * @param {string} string
5923
+ */
5924
+ function parseSVG(string) {
5925
+ return /** @type SVGElement */ ((new DOMParser().parseFromString(string, 'image/svg+xml')).firstChild);
5926
+ }
5927
+
5928
+ function ruler() {
5929
+ return parseSVG(`
5930
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="23" height="23" fill="currentColor">
5931
+ <rect fill="none" height="24" width="24"/>
5932
+ <path d="M20,6H4C2.9,6,2,6.9,2,8v8c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8C22,6.9,21.1,6,20,6z M20,16H4V8h3v3c0,0.55,0.45,1,1,1h0 c0.55,0,1-0.45,1-1V8h2v3c0,0.55,0.45,1,1,1h0c0.55,0,1-0.45,1-1V8h2v3c0,0.55,0.45,1,1,1h0c0.55,0,1-0.45,1-1V8h3V16z"/>
5933
+ </svg>
5934
+ `);
5935
+ }
5936
+
5937
+ const icons = {
5938
+ ruler,
5939
+ };
5940
+
5941
+ // index.ts
5942
+ function distance(from, to, options = {}) {
5943
+ var coordinates1 = getCoord(from);
5944
+ var coordinates2 = getCoord(to);
5945
+ var dLat = degreesToRadians(coordinates2[1] - coordinates1[1]);
5946
+ var dLon = degreesToRadians(coordinates2[0] - coordinates1[0]);
5947
+ var lat1 = degreesToRadians(coordinates1[1]);
5948
+ var lat2 = degreesToRadians(coordinates2[1]);
5949
+ var a = Math.pow(Math.sin(dLat / 2), 2) + Math.pow(Math.sin(dLon / 2), 2) * Math.cos(lat1) * Math.cos(lat2);
5950
+ return radiansToLength(
5951
+ 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)),
5952
+ options.units
5953
+ );
5954
+ }
5955
+ var turf_distance_default = distance;
5956
+
5957
+ /** @param {number} value */
5958
+ function defaultLabelFormat(value) {
5959
+ return value < 1
5960
+ ? `${(value * 1000).toFixed()} m`
5961
+ : `${value.toFixed(2)} km`;
5962
+ }
5963
+
5964
+ const sources = {
5965
+ line: 'mapbox-control-ruler-lines',
5966
+ points: 'mapbox-control-ruler-points',
5967
+ };
5968
+
5969
+ /**
5970
+ * @param {[number, number][]} coordinates
5971
+ * @returns {import('geojson').Feature<import('geojson').LineString>}
5972
+ */
5973
+ function toGeoJSONLine(coordinates) {
5974
+ return {
5975
+ type: 'Feature',
5976
+ properties: {},
5977
+ geometry: {
5978
+ type: 'LineString',
5979
+ coordinates,
5980
+ },
5981
+ };
5982
+ }
5983
+
5984
+ /**
5985
+ * @param {[number, number][]} coordinates
5986
+ * @param {{
5987
+ * units?: import('@turf/helpers').Units,
5988
+ * labelFormat?: (v: number) => string
5989
+ * }} options
5990
+ * @returns {import('geojson').FeatureCollection<import('geojson').Point>}
5991
+ */
5992
+ function toGeoJSONPoints(coordinates, options = {}) {
5993
+ const labelFormat = options.labelFormat ?? defaultLabelFormat;
5994
+ const units = options.units ?? 'kilometers';
5995
+ let sum = 0;
5996
+ return {
5997
+ type: 'FeatureCollection',
5998
+ features: coordinates.map((coordinate, index) => {
5999
+ if (index > 0) {
6000
+ sum += turf_distance_default(coordinates[index - 1], coordinate, { units });
6001
+ }
6002
+ return {
6003
+ type: 'Feature',
6004
+ id: String(index),
6005
+ properties: {
6006
+ distance: labelFormat(sum),
6007
+ },
6008
+ geometry: {
6009
+ type: 'Point',
6010
+ coordinates: coordinate,
6011
+ },
6012
+ };
6013
+ }),
6014
+ };
6015
+ }
6016
+
6017
+ /**
6018
+ * @typedef {{
6019
+ * line: import('mapbox-gl').LineLayerSpecification
6020
+ * markers: import('mapbox-gl').CircleLayerSpecification
6021
+ * labels: import('mapbox-gl').SymbolLayerSpecification
6022
+ * }} Layers
6023
+ */
6024
+
6025
+ /** @type {Layers} */
6026
+ const layers = {
6027
+ line: {
6028
+ id: 'mapbox-control-ruler-line',
6029
+ type: 'line',
6030
+ source: sources.line,
6031
+ layout: {},
6032
+ paint: {
6033
+ 'line-color': '#263238',
6034
+ 'line-width': 2,
6035
+ },
6036
+ },
6037
+ markers: {
6038
+ id: 'mapbox-control-ruler-markers',
6039
+ type: 'circle',
6040
+ source: sources.points,
6041
+ paint: {
6042
+ 'circle-radius': 5,
6043
+ 'circle-color': '#fff',
6044
+ 'circle-stroke-width': 2,
6045
+ 'circle-stroke-color': '#000',
6046
+ },
6047
+ },
6048
+ labels: {
6049
+ id: 'mapbox-control-ruler-labels',
6050
+ type: 'symbol',
6051
+ source: sources.points,
6052
+ layout: {
6053
+ 'text-field': '{distance}',
6054
+ 'text-font': ['Roboto Medium'],
6055
+ 'text-anchor': 'top',
6056
+ 'text-size': 12,
6057
+ 'text-offset': [0, 0.8],
6058
+ },
6059
+ paint: {
6060
+ 'text-color': '#263238',
6061
+ 'text-halo-color': '#fff',
6062
+ 'text-halo-width': 1,
6063
+ },
6064
+ },
6065
+ };
6066
+
6067
+ class RulerControl {
6068
+ /**
6069
+ * @param {import('./types').ControlOptions} options
6070
+ */
6071
+ constructor(options = {}) {
6072
+ this.options = options;
6073
+ this.container = controlContainer('mapbox-ctrl-ruler');
6074
+ this.isActive = false;
6075
+ /** @type {[number, number][]} */
6076
+ this.coordinates = [];
6077
+ /** @type {HTMLButtonElement | null} */
6078
+ this.button = null;
6079
+ /** @type {(() => void) | null} */
6080
+ this.removeDragEvents = null;
6081
+ if (!this.options.invisible) {
6082
+ this.button = controlButton({
6083
+ title: 'Ruler',
6084
+ icon: icons.ruler(),
6085
+ onClick: () => this.onControlButtonClick(),
6086
+ });
6087
+ }
6088
+ }
6089
+
6090
+ onControlButtonClick() {
6091
+ if (this.isActive) {
6092
+ this.deactivate();
6093
+ } else {
6094
+ this.activate();
6095
+ }
6096
+ }
6097
+
6098
+ draw = () => {
6099
+ if (!this.map) throw Error('map is undefined');
6100
+
6101
+ this.map.addSource(sources.line, {
6102
+ type: 'geojson',
6103
+ data: toGeoJSONLine(this.coordinates),
6104
+ });
6105
+
6106
+ this.map.addSource(sources.points, {
6107
+ type: 'geojson',
6108
+ data: toGeoJSONPoints(this.coordinates, {
6109
+ units: this.options.units,
6110
+ labelFormat: this.options.labelFormat,
6111
+ }),
6112
+ });
6113
+
6114
+ this.map.addLayer({
6115
+ ...layers.line,
6116
+ layout: {
6117
+ ...layers.line.layout,
6118
+ ...this.options.lineLayout,
6119
+ },
6120
+ paint: {
6121
+ ...layers.line.paint,
6122
+ ...this.options.linePaint,
6123
+ },
6124
+ });
6125
+
6126
+ this.map.addLayer({
6127
+ ...layers.markers,
6128
+ layout: {
6129
+ ...layers.markers.layout,
6130
+ ...this.options.markerLayout,
6131
+ },
6132
+ paint: {
6133
+ ...layers.markers.paint,
6134
+ ...this.options.markerPaint,
6135
+ },
6136
+ });
6137
+
6138
+ this.map.addLayer({
6139
+ ...layers.labels,
6140
+ layout: {
6141
+ ...layers.labels.layout,
6142
+ ...this.options.labelLayout,
6143
+ },
6144
+ paint: {
6145
+ ...layers.labels.paint,
6146
+ ...this.options.labelPaint,
6147
+ },
6148
+ });
6149
+ };
6150
+
6151
+ activate() {
6152
+ if (!this.map) throw Error('map is undefined');
6153
+ const map = this.map;
6154
+ this.isActive = true;
6155
+ this.coordinates = [];
6156
+ map.getCanvas().style.cursor = 'crosshair';
6157
+ this.draw();
6158
+ map.on('click', this.mapClickListener);
6159
+ map.on('style.load', this.draw);
6160
+ // @ts-ignore
6161
+ map.fire('ruler.on');
6162
+ if (this.button) {
6163
+ this.button.classList.add('-active');
6164
+ }
6165
+ }
6166
+
6167
+ deactivate() {
6168
+ if (!this.map) throw Error('map is undefined');
6169
+ this.isActive = false;
6170
+ this.map.getCanvas().style.cursor = '';
6171
+ // remove layers, sources and event listeners
6172
+ this.map.removeLayer(layers.line.id);
6173
+ this.map.removeLayer(layers.markers.id);
6174
+ this.map.removeLayer(layers.labels.id);
6175
+ this.map.removeSource(sources.line);
6176
+ this.map.removeSource(sources.points);
6177
+ this.map.off('click', this.mapClickListener);
6178
+ this.map.off('style.load', this.draw);
6179
+ // @ts-ignore
6180
+ this.map.fire('ruler.off');
6181
+ if (this.button) {
6182
+ this.button.classList.remove('-active');
6183
+ }
6184
+ }
6185
+
6186
+ /**
6187
+ * @param {import('mapbox-gl').MapMouseEvent} event
6188
+ */
6189
+ mapClickListener = (event) => {
6190
+ if (!this.map) throw Error('map is undefined');
6191
+ this.addCoordinate([event.lngLat.lng, event.lngLat.lat]);
6192
+ };
6193
+
6194
+ /**
6195
+ * @param {[number, number]} coordinate - [lng, lat] of new point
6196
+ */
6197
+ addCoordinate(coordinate) {
6198
+ if (!this.map) throw Error('map is undefined');
6199
+ if (!this.isActive) throw Error('ruler is not active');
6200
+ this.coordinates.push(coordinate);
6201
+ this.updateSource();
6202
+ }
6203
+
6204
+ updateSource() {
6205
+ if (!this.map) throw Error('map is undefined');
6206
+ // @ts-ignore
6207
+ this.map.fire('ruler.change', { coordinates: this.coordinates });
6208
+ const lineSource = /** @type {import('mapbox-gl').GeoJSONSource} */(this.map.getSource(sources.line));
6209
+ const pointsSource = /** @type {import('mapbox-gl').GeoJSONSource} */(this.map.getSource(sources.points));
6210
+ const geoJSONLine = toGeoJSONLine(this.coordinates);
6211
+ const geoJSONPoints = toGeoJSONPoints(this.coordinates, {
6212
+ units: this.options.units,
6213
+ labelFormat: this.options.labelFormat,
6214
+ });
6215
+ lineSource.setData(geoJSONLine);
6216
+ pointsSource.setData(geoJSONPoints);
6217
+ }
6218
+
6219
+ addDragEvents() {
6220
+ /** @typedef {import('mapbox-gl').MapMouseEvent} MapMouseEvent */
6221
+ /** @typedef {import('mapbox-gl').MapTouchEvent} MapTouchEvent */
6222
+ /** @typedef {import('mapbox-gl').MapMouseEvent} MapLayerMouseEvent */
6223
+ /** @typedef {import('mapbox-gl').MapTouchEvent} MapLayerTouchEvent */
6224
+ if (!this.map) throw Error('map is undefined');
6225
+ const self = this;
6226
+ const map = this.map;
6227
+ const canvas = map.getCanvas();
6228
+ /** @type {number} */
6229
+ let markerIndex;
6230
+
6231
+ function onMouseEnter() {
6232
+ canvas.style.cursor = 'move';
6233
+ }
6234
+
6235
+ function onMouseLeave() {
6236
+ canvas.style.cursor = '';
6237
+ }
6238
+
6239
+ /** @param {MapLayerMouseEvent | MapLayerTouchEvent} event */
6240
+ function onStart(event) {
6241
+ // do not block multi-touch actions
6242
+ if (event.type === 'touchstart' && event.points.length !== 1) {
6243
+ return;
6244
+ }
6245
+ event.preventDefault();
6246
+ const features = event.features;
6247
+ if (!features) return;
6248
+ markerIndex = Number(features[0].id);
6249
+ canvas.style.cursor = 'grabbing';
6250
+ // mouse events
6251
+ map.on('mousemove', onMove);
6252
+ map.on('mouseup', onEnd);
6253
+ // touch events
6254
+ map.on('touchmove', onMove);
6255
+ map.on('touchend', onEnd);
6256
+ }
6257
+
6258
+ /** @param {MapMouseEvent | MapTouchEvent} event */
6259
+ function onMove(event) {
6260
+ const coords = event.lngLat;
6261
+ canvas.style.cursor = 'grabbing';
6262
+ self.coordinates[markerIndex] = [coords.lng, coords.lat];
6263
+ self.updateSource();
6264
+ }
6265
+
6266
+ function onEnd() {
6267
+ // mouse events
6268
+ map.off('mousemove', onMove);
6269
+ map.off('mouseup', onEnd);
6270
+ // touch events
6271
+ map.off('touchmove', onMove);
6272
+ map.off('touchend', onEnd);
6273
+ }
6274
+
6275
+ // mouse events
6276
+ map.on('mouseenter', layers.markers.id, onMouseEnter);
6277
+ map.on('mouseleave', layers.markers.id, onMouseLeave);
6278
+ map.on('mousedown', layers.markers.id, onStart);
6279
+ // touch events
6280
+ map.on('touchstart', layers.markers.id, onStart);
6281
+
6282
+ this.removeDragEvents = () => {
6283
+ // mouse events
6284
+ map.off('mousedown', layers.markers.id, onStart);
6285
+ map.off('mousemove', onMove);
6286
+ map.off('mouseup', onEnd);
6287
+ map.off('mouseenter', layers.markers.id, onMouseEnter);
6288
+ map.off('mouseleave', layers.markers.id, onMouseLeave);
6289
+ // touch events
6290
+ map.off('touchstart', layers.markers.id, onStart);
6291
+ map.off('touchmove', onMove);
6292
+ map.off('touchend', onEnd);
6293
+ };
6294
+ }
6295
+
6296
+ /**
6297
+ * @param {import('mapbox-gl').Map} map
6298
+ * @returns {HTMLElement}
6299
+ */
6300
+ onAdd(map) {
6301
+ this.map = map;
6302
+ if (this.button) {
6303
+ this.container.appendChild(this.button);
6304
+ }
6305
+ this.addDragEvents();
6306
+ return this.container;
6307
+ }
6308
+
6309
+ onRemove() {
6310
+ if (this.isActive) {
6311
+ this.deactivate();
6312
+ }
6313
+ if (this.removeDragEvents) {
6314
+ this.removeDragEvents();
6315
+ }
6316
+ this.container.parentNode?.removeChild(this.container);
6317
+ }
6318
+ }
6319
+
5821
6320
  class CustomOptionsControl {
5822
6321
  constructor(args) {
5823
6322
  const { options, onConfirm } = args;
@@ -6504,9 +7003,6 @@ class TerrainToggleControl {
6504
7003
  const DrawCacheFeatureManager = {
6505
7004
  _SourceId: 'drawCache',
6506
7005
  _id: '',
6507
- constructor: (options) => {
6508
- if (options.SourceId) DrawCacheFeatureManager._SourceId = options.SourceId;
6509
- },
6510
7006
  on: (map, id) => {
6511
7007
  if (!map.getSource(DrawCacheFeatureManager._SourceId)) return
6512
7008
  console.log(`output->map.loaded()`, map.loaded());
@@ -6586,5 +7082,5 @@ const useDrawCache = () => {
6586
7082
  }
6587
7083
  };
6588
7084
 
6589
- export { CustomOptionsControl, CustomToggleControl, DrawCacheFeatureManager, RainteeConstants, RainteeGISUtil, RainteeSourceMapTool, RasterLayerControl, TerrainToggleControl, useDrawCache };
7085
+ export { CustomOptionsControl, CustomToggleControl, DrawCacheFeatureManager, RainteeConstants, RainteeGISUtil, RainteeSourceMapTool, RasterLayerControl, RulerControl, TerrainToggleControl, useDrawCache };
6590
7086
  //# sourceMappingURL=index.js.map