adds grid lines to print map
This commit is contained in:
@@ -9,7 +9,7 @@ export const Colors = {
|
||||
'steelblue': { area: '#4682B4' },
|
||||
'red' : { area: '#F00' },
|
||||
'lime' : { area: '#9CC222', line: '#566B13' },
|
||||
'gray': { area: '#000000', line: '#242734', alpha: 0.00001, stroke: '#000000' }
|
||||
'gray': { area: '#000000', line: '#3366CC', alpha: 0.00001, stroke: '#000000' }
|
||||
};
|
||||
|
||||
const SEC = 1000;
|
||||
|
||||
11
web/src/lib/vendor/leaflet-graticule/leaflet-auto-graticule.css
vendored
Normal file
11
web/src/lib/vendor/leaflet-graticule/leaflet-auto-graticule.css
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
.leaflet-grid-label .gridlabel-vert {
|
||||
margin-left: 8px;
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.leaflet-grid-label .gridlabel-vert,
|
||||
.leaflet-grid-label .gridlabel-horiz {
|
||||
padding-left:2px;
|
||||
text-shadow: -2px 0 #FFFFFF, 0 2px #FFFFFF, 2px 0 #FFFFFF, 0 -2px #FFFFFF;
|
||||
}
|
||||
233
web/src/lib/vendor/leaflet-graticule/leaflet-auto-graticule.ts
vendored
Normal file
233
web/src/lib/vendor/leaflet-graticule/leaflet-auto-graticule.ts
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
import { LatLngBounds, type LatLngExpression, type LayerOptions, type LeafletEventHandlerFnMap, type PolylineOptions, Map, LayerGroup, Util, Polyline, LatLng, marker, divIcon, latLngBounds } from 'leaflet';
|
||||
import './leaflet-auto-graticule.css';
|
||||
|
||||
export interface AutoGraticuleOptions extends LayerOptions {
|
||||
redraw: keyof LeafletEventHandlerFnMap,
|
||||
|
||||
/** Minimum distance between two lines in pixels */
|
||||
minDistance: number
|
||||
}
|
||||
|
||||
export default class AutoGraticule extends LayerGroup {
|
||||
|
||||
options: AutoGraticuleOptions = {
|
||||
redraw: 'moveend',
|
||||
minDistance: 100 // Minimum distance between two lines in pixels
|
||||
};
|
||||
|
||||
lineStyle: PolylineOptions = {
|
||||
stroke: true,
|
||||
color: '#333',
|
||||
opacity: 0.6,
|
||||
weight: 1,
|
||||
interactive: false
|
||||
};
|
||||
|
||||
_bounds!: LatLngBounds;
|
||||
|
||||
|
||||
constructor(options?: Partial<AutoGraticuleOptions>) {
|
||||
super();
|
||||
Util.setOptions(this, options);
|
||||
}
|
||||
|
||||
|
||||
onAdd(map: Map) {
|
||||
this._map = map;
|
||||
|
||||
this.redraw();
|
||||
this._map.on('viewreset ' + this.options.redraw, this.redraw, this);
|
||||
|
||||
this.eachLayer(map.addLayer, map);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
onRemove(map: Map) {
|
||||
map.off('viewreset ' + this.options.redraw, this.redraw, this);
|
||||
this.eachLayer(this.removeLayer, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
redraw() {
|
||||
this._bounds = this._map.getBounds().pad(0.5);
|
||||
|
||||
this.clearLayers();
|
||||
|
||||
this.constructLines();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
constructLines() {
|
||||
const bounds = this._map.getBounds();
|
||||
const zoom = this._map.getZoom();
|
||||
|
||||
// Fix drawing of lines outside of bounds
|
||||
this._bounds = AutoGraticule.bboxIntersect(bounds, [[-85, -180], [85, 180]]);
|
||||
|
||||
// Fix drawing of labels outside of bounds
|
||||
const getBoundsBkp = this._map.getBounds;
|
||||
try {
|
||||
this._map.getBounds = function () {
|
||||
return AutoGraticule.bboxIntersect(getBoundsBkp.apply(this), [[-85, -180], [85, 180]])
|
||||
};
|
||||
|
||||
// Longitude: Draw vartical lines with a fixed distance between each other
|
||||
const center = this._map.project(bounds.getCenter(), zoom);
|
||||
const divisor = AutoGraticule.getGridDivisor(this._map.unproject(center.add([this.options.minDistance / 2, 0]), zoom).lng - this._map.unproject(center.subtract([this.options.minDistance / 2, 0]), zoom).lng, false);
|
||||
const west = Math.max(bounds.getWest(), -180);
|
||||
const east = Math.min(bounds.getEast(), 180);
|
||||
for (let lng = AutoGraticule.fixFloatingPoint(Math.ceil(west / divisor) * divisor); lng <= east; lng += divisor) {
|
||||
this.addLayer(this.buildXLine(lng));
|
||||
this.addLayer(this.buildLabel('gridlabel-horiz', AutoGraticule.fixFloatingPoint(lng)));
|
||||
}
|
||||
|
||||
// Latitude: Draw horizontal lines with a variable distance between each other (as in Mercator projection, the distance
|
||||
// between latitudes gets bigger towards the poles). Calculate a divisor that all latitude line coordinates must be
|
||||
// dividable by, but then only draw those coordinates that keep the minimum distance to their neighbour lines.
|
||||
// Draw lines north of the equator separately from lines south of the equator (to keep the grid symmetical in
|
||||
// relation to the equator).
|
||||
if (bounds.getNorth() > 0) {
|
||||
let lat = Math.max(0, bounds.getSouth());
|
||||
let first = true;
|
||||
while (lat < bounds.getNorth() && lat < 85) {
|
||||
const point = this._map.project([lat, bounds.getCenter().lng], zoom);
|
||||
const point2LatLng = this._map.unproject(point.subtract([0, this.options.minDistance]), zoom);
|
||||
|
||||
const divisor = AutoGraticule.getGridDivisor(point2LatLng.lat - lat, true);
|
||||
lat = AutoGraticule.fixFloatingPoint(first ? Math.ceil(lat / divisor) * divisor : Math.ceil(point2LatLng.lat / divisor) * divisor);
|
||||
|
||||
first = false;
|
||||
|
||||
this.addLayer(this.buildYLine(lat));
|
||||
this.addLayer(this.buildLabel('gridlabel-vert', lat));
|
||||
}
|
||||
}
|
||||
if (bounds.getSouth() < 0) {
|
||||
let lat = Math.min(0, bounds.getNorth());
|
||||
let first = true;
|
||||
while (lat > bounds.getSouth() && lat > -85) {
|
||||
const point = this._map.project([lat, bounds.getCenter().lng], zoom);
|
||||
const point2LatLng = this._map.unproject(point.add([0, this.options.minDistance]), zoom);
|
||||
|
||||
const divisor = AutoGraticule.getGridDivisor(AutoGraticule.fixFloatingPoint(lat - point2LatLng.lat), true);
|
||||
lat = AutoGraticule.fixFloatingPoint(first ? Math.floor(lat / divisor) * divisor : Math.floor(point2LatLng.lat / divisor) * divisor);
|
||||
|
||||
first = false;
|
||||
|
||||
this.addLayer(this.buildYLine(lat));
|
||||
this.addLayer(this.buildLabel('gridlabel-vert', lat));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this._map.getBounds = getBoundsBkp;
|
||||
}
|
||||
}
|
||||
|
||||
buildXLine(x: number): Polyline {
|
||||
const bottomLL = new LatLng(this._bounds.getSouth(), x);
|
||||
const topLL = new LatLng(this._bounds.getNorth(), x);
|
||||
|
||||
return new Polyline([bottomLL, topLL], this.lineStyle);
|
||||
}
|
||||
|
||||
buildYLine(y: number): L.Polyline {
|
||||
const leftLL = new LatLng(y, this._bounds.getWest());
|
||||
const rightLL = new LatLng(y, this._bounds.getEast());
|
||||
|
||||
return new Polyline([leftLL, rightLL], this.lineStyle);
|
||||
}
|
||||
|
||||
buildLabel(axis: 'gridlabel-horiz' | 'gridlabel-vert', val: number) {
|
||||
const bounds = this._map.getBounds().pad(-0.003);
|
||||
let latLng: LatLng;
|
||||
if (axis == 'gridlabel-horiz') {
|
||||
latLng = new LatLng(bounds.getNorth(), val);
|
||||
} else {
|
||||
latLng = new LatLng(val, bounds.getWest());
|
||||
}
|
||||
|
||||
return marker(latLng, {
|
||||
interactive: false,
|
||||
icon: divIcon({
|
||||
iconSize: [0, 0],
|
||||
className: 'leaflet-grid-label',
|
||||
html: '<div class="' + axis + '">' + val + ' °</div>'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounds the given number to a fixed number of decimals in order to avoid floating point inaccuracies
|
||||
* (for example to make 0.1 + 0.2 = 0.3 instead of 0.30000000000000004).
|
||||
*/
|
||||
static fixFloatingPoint(number: number): number {
|
||||
return AutoGraticule.round(number, 12);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounds the given number to the given number of decimals.
|
||||
*/
|
||||
static round(number: number, digits: number) {
|
||||
const fac = Math.pow(10, digits);
|
||||
return Math.round(number * fac) / fac;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the distance between two coordinates, floors this distance to 90, 60, 45, 30 or
|
||||
* 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05, 0.02, 0.01, ...
|
||||
* This will define the distance between two grid lines.
|
||||
* @param variableDistance This should be true when the distance between the grid lines will be variable, that is for the latitude
|
||||
* lines (because in Meractor projection the distance between two latitutes gets larger towards the poles, but the distance
|
||||
* between two longitudes is constant throughout the globe).
|
||||
* For constant distance lines (longitude), a line should be shown for every coordinate dividable by the result of this
|
||||
* function. For example, if the result is 45, a line should be shown for longitudes -180, -135, -90, -45, 0, 45, 90, 135, 180.
|
||||
* For variable distance lines (latitude), a line should be shown for coordinates dividable by a result of this function, but
|
||||
* not for every single coordinate but only those that have a minimum distance towards their neighbour coordinate line. For
|
||||
* variable distance lines, the maximum number returned by this function is 5, meaning at low zoom levels, all latitude lines
|
||||
* are dividable by 5 (but not every latitude dividable by 5 will get a line).
|
||||
*/
|
||||
static getGridDivisor(number: number, variableDistance: boolean) {
|
||||
if (number <= 0 || !isFinite(number))
|
||||
throw new Error("Invalid number " + number);
|
||||
else {
|
||||
if (variableDistance && number >= 5)
|
||||
return 5;
|
||||
if (number <= 10) {
|
||||
let fac = 1;
|
||||
while (number > 1) { fac *= 10; number /= 10; }
|
||||
while (number <= 0.1) { fac /= 10; number *= 10; }
|
||||
|
||||
// Dist is now some number between 0.1 and 1, so we can round it conveniently and then multiply it again by fac to get back to the original dist
|
||||
|
||||
if (number == 0.1)
|
||||
return AutoGraticule.fixFloatingPoint(0.1 * fac);
|
||||
else if (number <= 0.2)
|
||||
return AutoGraticule.fixFloatingPoint(0.2 * fac);
|
||||
else if (number <= 0.5)
|
||||
return AutoGraticule.fixFloatingPoint(0.5 * fac);
|
||||
else
|
||||
return fac;
|
||||
} else if (number <= 30)
|
||||
return 30;
|
||||
else if (number <= 45)
|
||||
return 45;
|
||||
else if (number <= 60)
|
||||
return 60;
|
||||
else
|
||||
return 90;
|
||||
}
|
||||
}
|
||||
// Backwards compatibility
|
||||
static niceRound = AutoGraticule.getGridDivisor;
|
||||
|
||||
static bboxIntersect(bbox1: LatLngBounds | LatLngExpression[], bbox2: LatLngBounds | LatLngExpression[]) {
|
||||
const bounds1 = bbox1 instanceof LatLngBounds ? bbox1 : latLngBounds(bbox1);
|
||||
const bounds2 = bbox2 instanceof LatLngBounds ? bbox2 : latLngBounds(bbox2);
|
||||
return latLngBounds([
|
||||
[Math.max(bounds1.getSouth(), bounds2.getSouth()), Math.max(bounds1.getWest(), bounds2.getWest())],
|
||||
[Math.min(bounds1.getNorth(), bounds2.getNorth()), Math.min(bounds1.getEast(), bounds2.getEast())]
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -197,12 +197,11 @@ export default function leafletImage(map, callback) {
|
||||
minPoint = new L.Point(pixelBounds.min.x, pixelBounds.min.y),
|
||||
pixelPoint = map.project(marker.getLatLng()),
|
||||
url = "/vendor/leaflet-image/marker.png",
|
||||
icon = getComputedStyle(marker._icon.children[0], ':before').getPropertyValue('content').substring(1, 2) ?? "\uf111",
|
||||
im = new Image(),
|
||||
options = marker.options.icon.options,
|
||||
size = options.iconSize,
|
||||
pos = pixelPoint.subtract(minPoint),
|
||||
anchor = L.point(options.iconAnchor || size && size.divideBy(2, true));
|
||||
anchor = L.point(options.iconAnchor || size && { x: size[0], y: size[1] });
|
||||
|
||||
if (size instanceof L.Point) size = [size.x, size.y];
|
||||
|
||||
@@ -214,10 +213,28 @@ export default function leafletImage(map, callback) {
|
||||
im.crossOrigin = '';
|
||||
|
||||
im.onload = function () {
|
||||
ctx.drawImage(this, x, y, size[0], size[1]);
|
||||
ctx.font = '600 14px "Font Awesome 6 Free"';
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.fillText(icon, x + 10.2, y + 24);
|
||||
if (marker._icon.classList.contains("leaflet-grid-label")) {
|
||||
const label = marker._icon.children[0].textContent;
|
||||
ctx.font = '600 8px "Font Awesome 6 Free"';
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.fillText(label, x + 4, y + 12);
|
||||
} else {
|
||||
const waypointName = marker.options.meta?.waypointName;
|
||||
const icon = getComputedStyle(marker._icon.children[0], ':before').getPropertyValue('content').substring(1, 2) ?? "\uf111";
|
||||
ctx.drawImage(this, x, y, size[0], size[1]);
|
||||
ctx.font = '600 14px "Font Awesome 6 Free"';
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.fillText(icon, x + 10.2, y + 24);
|
||||
if (waypointName) {
|
||||
ctx.fillStyle = "#313131"
|
||||
ctx.roundRect(x - 8, y - 18, waypointName.length * 7, 20, 5)
|
||||
ctx.fill();
|
||||
ctx.font = '600 12px "IBMPlexSans"';
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.fillText(waypointName, x, y - 4);
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, {
|
||||
canvas: canvas
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user