leaflet-grid-distance
Advanced tools
Comparing version 1.0.0 to 2.0.1
import * as L from 'leaflet'; | ||
interface AxesLayerOptions extends L.GridLayerOptions { | ||
cells: number; | ||
color: string; | ||
axesColor: string; | ||
axesWidth: number; | ||
zoom: number; | ||
showLabel: boolean; | ||
defaultLabel: { | ||
color: string; | ||
size: number; | ||
}; | ||
center: L.LatLngLiteral; | ||
primaryColor: string; | ||
secondaryColor: string; | ||
textColor: string; | ||
fontSize: number; | ||
kmThreshold: number; | ||
} | ||
declare function AxesLayer(opts: Partial<AxesLayerOptions>): L.GridLayer; | ||
export { AxesLayer, AxesLayerOptions }; | ||
declare function AxesLayerWithDistance(opts: Partial<AxesLayerOptions>): L.GridLayer; | ||
export { AxesLayerWithDistance, AxesLayerOptions }; |
@@ -26,143 +26,155 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.AxesLayer = AxesLayer; | ||
exports.AxesLayerWithDistance = AxesLayerWithDistance; | ||
const L = __importStar(require("leaflet")); | ||
const AxesLayerWithDistance = L.GridLayer.extend({ | ||
const AxesLayerWithDistanceClass = L.GridLayer.extend({ | ||
options: { | ||
cells: 5, | ||
color: '#40404044', | ||
axesColor: '#ff6754', | ||
axesWidth: 0.8, | ||
zoom: 10, | ||
showLabel: false, | ||
defaultLabel: { | ||
color: '#404040', | ||
size: 13, | ||
}, | ||
center: { lat: 0, lng: 0 }, | ||
tileSize: 256, | ||
primaryColor: '#ff0000', | ||
secondaryColor: '#999999', | ||
textColor: '#000000', | ||
fontSize: 12, | ||
kmThreshold: 13, | ||
}, | ||
initialize: function (options) { | ||
L.setOptions(this, options); | ||
L.GridLayer.prototype.initialize.call(this, options); | ||
this._center = null; | ||
}, | ||
onAdd: function (map) { | ||
L.GridLayer.prototype.onAdd.call(this, map); | ||
this.setCenter(this.options.center); | ||
this._center = map.getCenter(); | ||
map.on('move', this._onMove, this); | ||
}, | ||
setCenter: function (center) { | ||
this.options.center = L.latLng(center); | ||
onRemove: function (map) { | ||
map.off('move', this._onMove, this); | ||
L.GridLayer.prototype.onRemove.call(this, map); | ||
}, | ||
// eslint-disable-next-line unused-imports/no-unused-vars | ||
_onMove: function (e) { | ||
this._center = this._map.getCenter(); | ||
this.redraw(); | ||
}, | ||
createTile: function (coords) { | ||
const svg = this.createParentTile(); | ||
const n = this.options.cells; | ||
createTile: function (coords, done) { | ||
const tile = L.DomUtil.create('canvas', 'leaflet-tile'); | ||
const size = this.getTileSize(); | ||
tile.width = size.x; | ||
tile.height = size.y; | ||
const ctx = tile.getContext('2d'); | ||
// Ensure _center is defined before drawing | ||
if (!this._center && this._map) { | ||
this._center = this._map.getCenter(); | ||
} | ||
if (this._center) { | ||
this._drawGrid(ctx, coords, size); | ||
} | ||
setTimeout(() => { | ||
done(null, tile); | ||
}, 0); | ||
return tile; | ||
}, | ||
_drawGrid: function (ctx, coords, size) { | ||
const zoom = this._map.getZoom(); | ||
const centerPoint = this._map.project(this._center, zoom); | ||
const tilePoint = coords.scaleBy(size); | ||
// Draw grid lines | ||
ctx.strokeStyle = this.options.secondaryColor; | ||
ctx.lineWidth = 1; | ||
const gridSize = size.x / 4; // Since we're drawing 4x4 grid | ||
for (let i = 0; i <= size.x; i += gridSize) { | ||
ctx.beginPath(); | ||
ctx.moveTo(i, 0); | ||
ctx.lineTo(i, size.y); | ||
ctx.stroke(); | ||
ctx.beginPath(); | ||
ctx.moveTo(0, i); | ||
ctx.lineTo(size.x, i); | ||
ctx.stroke(); | ||
} | ||
// Calculate axis positions | ||
let xAxisY = centerPoint.y - tilePoint.y; | ||
let yAxisX = centerPoint.x - tilePoint.x; | ||
// Snap axes to nearest grid line | ||
xAxisY = Math.round(xAxisY / gridSize) * gridSize; | ||
yAxisX = Math.round(yAxisX / gridSize) * gridSize; | ||
// Draw main axes | ||
ctx.strokeStyle = this.options.primaryColor; | ||
ctx.lineWidth = 2; | ||
// Draw X-axis (horizontal line) | ||
if (xAxisY >= 0 && xAxisY <= size.y) { | ||
ctx.beginPath(); | ||
ctx.moveTo(0, xAxisY); | ||
ctx.lineTo(size.x, xAxisY); | ||
ctx.stroke(); | ||
} | ||
// Draw Y-axis (vertical line) | ||
if (yAxisX >= 0 && yAxisX <= size.x) { | ||
ctx.beginPath(); | ||
ctx.moveTo(yAxisX, 0); | ||
ctx.lineTo(yAxisX, size.y); | ||
ctx.stroke(); | ||
} | ||
// Draw distance labels | ||
this._drawDistanceLabels(ctx, coords, size, centerPoint, zoom); | ||
}, | ||
_drawDistanceLabels: function (ctx, coords, size, centerPoint, zoom) { | ||
const tileSize = this.getTileSize(); | ||
const nwPoint = coords.scaleBy(tileSize); | ||
const sePoint = nwPoint.add(tileSize); | ||
const nw = this._map.unproject(nwPoint, coords.z); | ||
const se = this._map.unproject(sePoint, coords.z); | ||
const tileDiagonalDistance = this._map.distance(nw, se); | ||
const tileEdgeDistance = tileDiagonalDistance / Math.sqrt(2); | ||
const centerPoint = this._map.project(this.options.center, coords.z); | ||
ctx.fillStyle = this.options.textColor; | ||
ctx.font = `${this.options.fontSize}px Arial`; | ||
ctx.textAlign = 'center'; | ||
ctx.textBaseline = 'middle'; | ||
const tilePoint = coords.scaleBy(tileSize); | ||
const relativePoint = tilePoint.subtract(centerPoint); | ||
const size = 256 / n; | ||
for (let i = 0; i < n; i++) { | ||
for (let j = 0; j < n; j++) { | ||
const tile = this.Rect(i * size, j * size, size, size); | ||
svg.appendChild(tile); | ||
} | ||
const xAxisY = centerPoint.y - tilePoint.y; | ||
const yAxisX = centerPoint.x - tilePoint.x; | ||
// Calculate the distance represented by one tile | ||
const tileCenterPoint = tilePoint.add([tileSize.x / 2, tileSize.y / 2]); | ||
let metersPerPixel; | ||
try { | ||
const tileCenterLatLng = this._map.unproject(tileCenterPoint, coords.z); | ||
const tileEdgeLatLng = this._map.unproject(tileCenterPoint.add([tileSize.x / 2, 0]), coords.z); | ||
const tileWidthMeters = tileCenterLatLng.distanceTo(tileEdgeLatLng) * 2; | ||
metersPerPixel = tileWidthMeters / tileSize.x; | ||
} | ||
svg.setAttributeNS(null, 'id', coords.x.toString()); | ||
if (Math.abs(relativePoint.x) < 128) { | ||
const weight = this.options.axesWidth; | ||
const x = 128 - relativePoint.x; | ||
const line = this.Line(x - weight / 2, 0, x - weight / 2, 256, weight); | ||
svg.appendChild(line); | ||
catch (error) { | ||
console.warn('Error calculating distance:', error); | ||
return; // Exit the function if we can't calculate the distance | ||
} | ||
if (Math.abs(relativePoint.y) < 128) { | ||
const weight = this.options.axesWidth; | ||
const y = 128 + relativePoint.y; | ||
const line = this.Line(0, y - weight / 2, 256, y - weight / 2, weight); | ||
svg.appendChild(line); | ||
// Determine unit based on the zoom level | ||
const useKilometers = zoom <= this.options.kmThreshold; | ||
const unit = useKilometers ? 'km' : 'm'; | ||
const unitMultiplier = useKilometers ? 0.001 : 1; | ||
// Function to format distance | ||
const formatDistance = (distanceMeters) => { | ||
const distance = distanceMeters * unitMultiplier; | ||
return useKilometers ? distance.toFixed(2) : Math.round(distance); | ||
}; | ||
// X-axis labels | ||
if (xAxisY >= 0 && xAxisY <= size.y) { | ||
for (let i = 0; i <= size.x; i += size.x / 4) { | ||
const pixelDistance = Math.abs(i - yAxisX); | ||
const distanceMeters = pixelDistance * metersPerPixel; | ||
const formattedDistance = formatDistance(distanceMeters); | ||
ctx.fillText(`${formattedDistance}${unit}`, i, xAxisY + 20); | ||
} | ||
} | ||
if (this.options.showLabel) { | ||
const size = this.options.defaultLabel.size; | ||
const color = this.options.defaultLabel.color; | ||
const tileNW = this._map.unproject(coords.scaleBy(tileSize), coords.z); | ||
const baseDistance = this._map.distance(this.options.center, tileNW); | ||
if (Math.abs(relativePoint.y) < 128) { | ||
for (let i = 0; i < n; i++) { | ||
const offsetX = (i - n / 2) * tileEdgeDistance / n; | ||
const distance = baseDistance + offsetX; | ||
const text = this.Text(this.normalize(distance), (i * 256) / n, 128 + relativePoint.y, color, size - 2); | ||
svg.appendChild(text); | ||
} | ||
// Y-axis labels | ||
if (yAxisX >= 0 && yAxisX <= size.x) { | ||
for (let i = 0; i <= size.y; i += size.y / 4) { | ||
const pixelDistance = Math.abs(i - xAxisY); | ||
const distanceMeters = pixelDistance * metersPerPixel; | ||
const formattedDistance = formatDistance(distanceMeters); | ||
ctx.fillText(`${formattedDistance}${unit}`, yAxisX + 20, i); | ||
} | ||
if (Math.abs(relativePoint.x) < 128) { | ||
for (let i = 0; i < n; i++) { | ||
const offsetY = (n / 2 - i) * tileEdgeDistance / n; | ||
const distance = baseDistance + offsetY; | ||
const text = this.Text(this.normalize(distance), 128 - relativePoint.x, (i * 256) / n, color, size - 2); | ||
svg.appendChild(text); | ||
} | ||
} | ||
if (Math.abs(relativePoint.x) < 128 && Math.abs(relativePoint.y) < 128) { | ||
const text = this.Text('0,0', 128 - relativePoint.x, 128 + relativePoint.y, color, size); | ||
svg.appendChild(text); | ||
} | ||
} | ||
return svg; | ||
}, | ||
normalize(distanceInMeters) { | ||
let val; | ||
let unit; | ||
if (Math.abs(distanceInMeters) >= 1000) { | ||
val = distanceInMeters / 1000; | ||
unit = 'km'; | ||
} | ||
else { | ||
val = distanceInMeters; | ||
unit = 'm'; | ||
} | ||
val = Math.round(val * 100) / 100; | ||
return `${val}${unit}`; | ||
_getScale: function (zoom) { | ||
const centerLatLng = this._center || this._map.getCenter(); | ||
const pointC = this._map.project(centerLatLng, zoom); | ||
const pointX = L.point(pointC.x + this.options.tileSize / 2, pointC.y); | ||
const latLngC = this._map.unproject(pointC, zoom); | ||
const latLngX = this._map.unproject(pointX, zoom); | ||
return latLngC.distanceTo(latLngX) / (this.options.tileSize / 2); | ||
}, | ||
createParentTile() { | ||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); | ||
svg.setAttributeNS('http://www.w3.org/2000/svg', 'xlink', 'http://www.w3.org/1999/xlink'); | ||
svg.setAttributeNS('http://www.w3.org/2000/svg', 'height', '256'); | ||
svg.setAttributeNS('http://www.w3.org/2000/svg', 'width', '256'); | ||
const rect = this.Rect(0, 0, 256, 256, 2); | ||
svg.appendChild(rect); | ||
return svg; | ||
}, | ||
Rect(x, y, width, height, weight = 0.5) { | ||
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); | ||
rect.setAttributeNS(null, 'height', height.toString()); | ||
rect.setAttributeNS(null, 'width', width.toString()); | ||
rect.setAttributeNS(null, 'x', x.toString()); | ||
rect.setAttributeNS(null, 'y', y.toString()); | ||
rect.setAttributeNS(null, 'fill', 'none'); | ||
rect.setAttributeNS(null, 'stroke', this.options.color); | ||
rect.setAttributeNS(null, 'stroke-width', weight.toString()); | ||
return rect; | ||
}, | ||
Line(x1, y1, x2, y2, weight = 1) { | ||
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); | ||
line.setAttributeNS(null, 'x1', x1.toString()); | ||
line.setAttributeNS(null, 'y1', y1.toString()); | ||
line.setAttributeNS(null, 'x2', x2.toString()); | ||
line.setAttributeNS(null, 'y2', y2.toString()); | ||
line.setAttributeNS(null, 'stroke', this.options.axesColor); | ||
line.setAttributeNS(null, 'stroke-width', weight.toString()); | ||
return line; | ||
}, | ||
Text(text, x, y, color, size) { | ||
const txt = document.createElementNS('http://www.w3.org/2000/svg', 'text'); | ||
txt.setAttributeNS(null, 'x', x.toString()); | ||
txt.setAttributeNS(null, 'y', y.toString()); | ||
txt.setAttributeNS(null, 'fill', color); | ||
txt.setAttributeNS(null, 'font-size', size.toString()); | ||
txt.textContent = text; | ||
return txt; | ||
}, | ||
}); | ||
function AxesLayer(opts) { | ||
return new AxesLayerWithDistance(opts); | ||
function AxesLayerWithDistance(opts) { | ||
return new AxesLayerWithDistanceClass(opts); | ||
} |
{ | ||
"name": "leaflet-grid-distance", | ||
"version": "1.0.0", | ||
"version": "2.0.1", | ||
"description": "A Leaflet plugin to show grid distances", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -13,3 +13,3 @@ # AxesLayerWithDistance | ||
- **Dynamic Centering**: Set and update the center of the grid dynamically. | ||
- **SVG Rendering**: Utilizes SVG for sharp and scalable graphics. | ||
- **Canvas Rendering**: Utilizes HTML5 Canvas for efficient rendering. | ||
@@ -34,3 +34,3 @@ ## Demo | ||
import * as L from 'leaflet'; | ||
import { AxesLayer, AxesLayerOptions } from 'axes-layer-with-distance'; | ||
import { AxesLayerWithDistance, AxesLayerOptions } from 'axes-layer-with-distance'; | ||
@@ -52,21 +52,14 @@ ``` | ||
// Create an AxesLayer with options | ||
const axesLayer = AxesLayer({ | ||
cells: 5, | ||
color: '#40404044', | ||
axesColor: '#ff6754', | ||
axesWidth: 0.8, | ||
zoom: 10, | ||
showLabel: true, | ||
defaultLabel: { | ||
color: '#404040', | ||
size: 13, | ||
}, | ||
center: { lat: 51.505, lng: -0.09 }, | ||
// Create an AxesLayerWithDistance with options | ||
const axesLayerWithDistance = AxesLayerWithDistance({ | ||
primaryColor: '#ff0000', | ||
secondaryColor: '#999999', | ||
textColor: '#000000', | ||
fontSize: 12, | ||
kmThreshold: 13, | ||
}); | ||
// Add the AxesLayer to the map | ||
axesLayer.addTo(map); | ||
// Add the AxesLayerWithDistance to the map | ||
axesLayerWithDistance.addTo(map); | ||
``` | ||
@@ -77,58 +70,11 @@ | ||
- **cells**: Number of grid cells per tile. Default is 5. | ||
- **color** : Color of the grid lines. Supports any CSS color value. Default is '#40404044'. | ||
- **axesColor**: Color of the main axes lines. Default is '#ff6754'. | ||
- **axesWidth**: Width of the axes lines in pixels. Default is 0.8. | ||
- **zoom**: Initial zoom level when adding the layer. Default is 10. | ||
- **showLabel**: Boolean to show/hide distance labels. Default is false. | ||
- **defaultLabel.color**: Color of the text labels. Default is '#404040'. | ||
- **defaultLabel.size**: Font size of the text labels. Default is 13. | ||
- **center**: The center of the grid, specified as a L.LatLngLiteral with latitude and longitude. | ||
- **primaryColor**: Color of the main axes lines. Default is '#ff0000'. | ||
- **secondaryColor**: Color of the grid lines. Default is '#999999'. | ||
- **textColor**: Color of the text labels. Default is '#000000'. | ||
- **fontSize**: Font size of the text labels. Default is 12. | ||
- **kmThreshold**: Zoom level threshold for switching between km and m units. Default is 13. | ||
## Methods | ||
- **setCenter(center: L.LatLngExpression)**: void: Updates the center of the grid and redraws the layer. | ||
- The AxesLayerWithDistance extends Leaflet's GridLayer and inherits all its methods. There are no additional public methods specific to AxesLayerWithDistance. | ||
## Advanced Example | ||
Here is a more advanced usage scenario, including event handling and dynamic updates: | ||
``` | ||
// Create a map with an initial view | ||
const map = L.map('map').setView([40.7128, -74.0060], 12); | ||
// Add OpenStreetMap tile layer | ||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | ||
attribution: '© OpenStreetMap contributors', | ||
maxZoom: 18, | ||
}).addTo(map); | ||
// Configure the AxesLayer options | ||
const options = { | ||
cells: 6, | ||
color: '#00000044', | ||
axesColor: '#008000', | ||
axesWidth: 1.2, | ||
zoom: 12, | ||
showLabel: true, | ||
defaultLabel: { | ||
color: '#008000', | ||
size: 14, | ||
}, | ||
center: { lat: 40.7128, lng: -74.0060 }, | ||
}; | ||
// Initialize the AxesLayer | ||
const axesLayer = AxesLayer(options); | ||
// Add the AxesLayer to the map | ||
axesLayer.addTo(map); | ||
// Update the grid center dynamically | ||
map.on('click', (event) => { | ||
axesLayer.setCenter(event.latlng); | ||
console.log('Grid center updated:', event.latlng); | ||
}); | ||
``` | ||
## Contributing | ||
@@ -135,0 +81,0 @@ We welcome contributions to improve this library. Please fork the repository, create a branch, and submit a pull request with your changes. |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
207
13452
7
84
1