adds grid lines to print map
This commit is contained in:
789
web/package-lock.json
generated
789
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -46,6 +46,7 @@
|
|||||||
"leaflet.awesome-markers": "^2.0.5",
|
"leaflet.awesome-markers": "^2.0.5",
|
||||||
"meilisearch": "^0.37.0",
|
"meilisearch": "^0.37.0",
|
||||||
"nouislider": "^15.7.1",
|
"nouislider": "^15.7.1",
|
||||||
|
"pdfkit": "^0.15.0",
|
||||||
"photoswipe": "^5.4.3",
|
"photoswipe": "^5.4.3",
|
||||||
"pocketbase": "^0.21.0",
|
"pocketbase": "^0.21.0",
|
||||||
"qrcode-with-logos": "^1.0.5",
|
"qrcode-with-logos": "^1.0.5",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 10 KiB |
@@ -25,6 +25,7 @@
|
|||||||
await import("leaflet.awesome-markers");
|
await import("leaflet.awesome-markers");
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
await import("$lib/vendor/leaflet-elevation/src/index.js");
|
await import("$lib/vendor/leaflet-elevation/src/index.js");
|
||||||
|
const AutoGraticule = (await import("$lib/vendor/leaflet-graticule/leaflet-auto-graticule")).default;
|
||||||
|
|
||||||
map = L.map("map", { preferCanvas: true }).setView(
|
map = L.map("map", { preferCanvas: true }).setView(
|
||||||
[trail.lat ?? 0, trail.lon ?? 0],
|
[trail.lat ?? 0, trail.lon ?? 0],
|
||||||
@@ -92,6 +93,7 @@
|
|||||||
iconColor: "white",
|
iconColor: "white",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
graticule: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const elevation_options = Object.assign(
|
const elevation_options = Object.assign(
|
||||||
@@ -106,6 +108,11 @@
|
|||||||
marker.addTo(map!);
|
marker.addTo(map!);
|
||||||
markers.push(marker);
|
markers.push(marker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(elevation_options.graticule) {
|
||||||
|
new AutoGraticule().addTo(map!);
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,11 @@
|
|||||||
"metric": "Metrisch",
|
"metric": "Metrisch",
|
||||||
"moderate": "Mittel",
|
"moderate": "Mittel",
|
||||||
"must-be-at-least-n-characters-long": "Muss mindestens {n} Zeichen lang sein",
|
"must-be-at-least-n-characters-long": "Muss mindestens {n} Zeichen lang sein",
|
||||||
|
"n-days-ago": "",
|
||||||
|
"n-hours-ago": "",
|
||||||
|
"n-minutes-ago": "",
|
||||||
|
"n-months-ago": "",
|
||||||
|
"n-seconds-ago": "",
|
||||||
"n-years-ago": "",
|
"n-years-ago": "",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"near": "Nahe",
|
"near": "Nahe",
|
||||||
@@ -99,6 +104,7 @@
|
|||||||
"pick-a-trail": "Route auswählen",
|
"pick-a-trail": "Route auswählen",
|
||||||
"polish": "Polnisch",
|
"polish": "Polnisch",
|
||||||
"portuguese": "Portugiesisch",
|
"portuguese": "Portugiesisch",
|
||||||
|
"print": "",
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"public": "Öffentlich",
|
"public": "Öffentlich",
|
||||||
"radius": "Radius",
|
"radius": "Radius",
|
||||||
|
|||||||
@@ -83,12 +83,12 @@
|
|||||||
"metric": "Metric",
|
"metric": "Metric",
|
||||||
"moderate": "Moderate",
|
"moderate": "Moderate",
|
||||||
"must-be-at-least-n-characters-long": "Must be at least {n} characters long",
|
"must-be-at-least-n-characters-long": "Must be at least {n} characters long",
|
||||||
"n-years-ago": "{n} years ago",
|
|
||||||
"n-months-ago": "{n} months ago",
|
|
||||||
"n-days-ago": "{n} days ago",
|
"n-days-ago": "{n} days ago",
|
||||||
"n-hours-ago": "{n} hours ago",
|
"n-hours-ago": "{n} hours ago",
|
||||||
"n-minutes-ago": "{n} minutes ago",
|
"n-minutes-ago": "{n} minutes ago",
|
||||||
|
"n-months-ago": "{n} months ago",
|
||||||
"n-seconds-ago": "{n} seconds ago",
|
"n-seconds-ago": "{n} seconds ago",
|
||||||
|
"n-years-ago": "{n} years ago",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"near": "Near",
|
"near": "Near",
|
||||||
"new-list": "New List",
|
"new-list": "New List",
|
||||||
@@ -104,6 +104,7 @@
|
|||||||
"pick-a-trail": "Pick a trail",
|
"pick-a-trail": "Pick a trail",
|
||||||
"polish": "Polish",
|
"polish": "Polish",
|
||||||
"portuguese": "Portuguese",
|
"portuguese": "Portuguese",
|
||||||
|
"print": "Print",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
"public": "Public",
|
"public": "Public",
|
||||||
"radius": "Radius",
|
"radius": "Radius",
|
||||||
|
|||||||
@@ -83,6 +83,11 @@
|
|||||||
"metric": "Métrique",
|
"metric": "Métrique",
|
||||||
"moderate": "Modérer",
|
"moderate": "Modérer",
|
||||||
"must-be-at-least-n-characters-long": "Doit être composé d'au moins {n} caractères",
|
"must-be-at-least-n-characters-long": "Doit être composé d'au moins {n} caractères",
|
||||||
|
"n-days-ago": "",
|
||||||
|
"n-hours-ago": "",
|
||||||
|
"n-minutes-ago": "",
|
||||||
|
"n-months-ago": "",
|
||||||
|
"n-seconds-ago": "",
|
||||||
"n-years-ago": "",
|
"n-years-ago": "",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
"near": "Proche",
|
"near": "Proche",
|
||||||
@@ -99,6 +104,7 @@
|
|||||||
"pick-a-trail": "Choisir un itinéraire",
|
"pick-a-trail": "Choisir un itinéraire",
|
||||||
"polish": "polonais",
|
"polish": "polonais",
|
||||||
"portuguese": "Portugais",
|
"portuguese": "Portugais",
|
||||||
|
"print": "",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
"public": "Publique",
|
"public": "Publique",
|
||||||
"radius": "Rayon",
|
"radius": "Rayon",
|
||||||
|
|||||||
@@ -83,6 +83,11 @@
|
|||||||
"metric": "Metrikus",
|
"metric": "Metrikus",
|
||||||
"moderate": "Mérsékelt",
|
"moderate": "Mérsékelt",
|
||||||
"must-be-at-least-n-characters-long": "Legalább {n} karakter hosszúnak kell lennie",
|
"must-be-at-least-n-characters-long": "Legalább {n} karakter hosszúnak kell lennie",
|
||||||
|
"n-days-ago": "",
|
||||||
|
"n-hours-ago": "",
|
||||||
|
"n-minutes-ago": "",
|
||||||
|
"n-months-ago": "",
|
||||||
|
"n-seconds-ago": "",
|
||||||
"n-years-ago": "",
|
"n-years-ago": "",
|
||||||
"name": "Név",
|
"name": "Név",
|
||||||
"near": "Közelben",
|
"near": "Közelben",
|
||||||
@@ -99,6 +104,7 @@
|
|||||||
"pick-a-trail": "Válasszon útvonalat",
|
"pick-a-trail": "Válasszon útvonalat",
|
||||||
"polish": "Lengyel",
|
"polish": "Lengyel",
|
||||||
"portuguese": "Portugál",
|
"portuguese": "Portugál",
|
||||||
|
"print": "",
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"public": "Publikus",
|
"public": "Publikus",
|
||||||
"radius": "Átmérő",
|
"radius": "Átmérő",
|
||||||
|
|||||||
@@ -83,6 +83,11 @@
|
|||||||
"metric": "Metrisch",
|
"metric": "Metrisch",
|
||||||
"moderate": "Gemiddeld",
|
"moderate": "Gemiddeld",
|
||||||
"must-be-at-least-n-characters-long": "Minimaal {n} tekens",
|
"must-be-at-least-n-characters-long": "Minimaal {n} tekens",
|
||||||
|
"n-days-ago": "",
|
||||||
|
"n-hours-ago": "",
|
||||||
|
"n-minutes-ago": "",
|
||||||
|
"n-months-ago": "",
|
||||||
|
"n-seconds-ago": "",
|
||||||
"n-years-ago": "",
|
"n-years-ago": "",
|
||||||
"name": "Naam",
|
"name": "Naam",
|
||||||
"near": "Nabij",
|
"near": "Nabij",
|
||||||
@@ -99,6 +104,7 @@
|
|||||||
"pick-a-trail": "Kies een wandelroute",
|
"pick-a-trail": "Kies een wandelroute",
|
||||||
"polish": "Pools",
|
"polish": "Pools",
|
||||||
"portuguese": "Portugees",
|
"portuguese": "Portugees",
|
||||||
|
"print": "",
|
||||||
"profile": "Profiel",
|
"profile": "Profiel",
|
||||||
"public": "Openbaar",
|
"public": "Openbaar",
|
||||||
"radius": "Straal",
|
"radius": "Straal",
|
||||||
|
|||||||
@@ -83,6 +83,11 @@
|
|||||||
"metric": "Metryczne",
|
"metric": "Metryczne",
|
||||||
"moderate": "Średnia",
|
"moderate": "Średnia",
|
||||||
"must-be-at-least-n-characters-long": "Długość musi wynosić przynajmniej {n} znaków",
|
"must-be-at-least-n-characters-long": "Długość musi wynosić przynajmniej {n} znaków",
|
||||||
|
"n-days-ago": "",
|
||||||
|
"n-hours-ago": "",
|
||||||
|
"n-minutes-ago": "",
|
||||||
|
"n-months-ago": "",
|
||||||
|
"n-seconds-ago": "",
|
||||||
"n-years-ago": "",
|
"n-years-ago": "",
|
||||||
"name": "Nazwa",
|
"name": "Nazwa",
|
||||||
"near": "Blisko",
|
"near": "Blisko",
|
||||||
@@ -99,6 +104,7 @@
|
|||||||
"pick-a-trail": "Wybierz ścieżkę",
|
"pick-a-trail": "Wybierz ścieżkę",
|
||||||
"polish": "Polski",
|
"polish": "Polski",
|
||||||
"portuguese": "Portugalski",
|
"portuguese": "Portugalski",
|
||||||
|
"print": "",
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"public": "Publiczny",
|
"public": "Publiczny",
|
||||||
"radius": "Promień",
|
"radius": "Promień",
|
||||||
|
|||||||
@@ -83,6 +83,11 @@
|
|||||||
"metric": "Métrica",
|
"metric": "Métrica",
|
||||||
"moderate": "Moderado",
|
"moderate": "Moderado",
|
||||||
"must-be-at-least-n-characters-long": "Deve ter pelo menos {n} caracteres",
|
"must-be-at-least-n-characters-long": "Deve ter pelo menos {n} caracteres",
|
||||||
|
"n-days-ago": "",
|
||||||
|
"n-hours-ago": "",
|
||||||
|
"n-minutes-ago": "",
|
||||||
|
"n-months-ago": "",
|
||||||
|
"n-seconds-ago": "",
|
||||||
"n-years-ago": "",
|
"n-years-ago": "",
|
||||||
"name": "Nome",
|
"name": "Nome",
|
||||||
"near": "Próximo",
|
"near": "Próximo",
|
||||||
@@ -99,6 +104,7 @@
|
|||||||
"pick-a-trail": "Escolha uma trilha",
|
"pick-a-trail": "Escolha uma trilha",
|
||||||
"polish": "Polonês",
|
"polish": "Polonês",
|
||||||
"portuguese": "Português",
|
"portuguese": "Português",
|
||||||
|
"print": "",
|
||||||
"profile": "Perfil",
|
"profile": "Perfil",
|
||||||
"public": "Público",
|
"public": "Público",
|
||||||
"radius": "Raio",
|
"radius": "Raio",
|
||||||
|
|||||||
@@ -83,6 +83,11 @@
|
|||||||
"metric": "公制",
|
"metric": "公制",
|
||||||
"moderate": "中等",
|
"moderate": "中等",
|
||||||
"must-be-at-least-n-characters-long": "长度至少 {n} 字符",
|
"must-be-at-least-n-characters-long": "长度至少 {n} 字符",
|
||||||
|
"n-days-ago": "",
|
||||||
|
"n-hours-ago": "",
|
||||||
|
"n-minutes-ago": "",
|
||||||
|
"n-months-ago": "",
|
||||||
|
"n-seconds-ago": "",
|
||||||
"n-years-ago": "",
|
"n-years-ago": "",
|
||||||
"name": "名称",
|
"name": "名称",
|
||||||
"near": "附近",
|
"near": "附近",
|
||||||
@@ -99,6 +104,7 @@
|
|||||||
"pick-a-trail": "选择一个路线",
|
"pick-a-trail": "选择一个路线",
|
||||||
"polish": "波兰语",
|
"polish": "波兰语",
|
||||||
"portuguese": "葡萄牙语",
|
"portuguese": "葡萄牙语",
|
||||||
|
"print": "",
|
||||||
"profile": "个人资料",
|
"profile": "个人资料",
|
||||||
"public": "公开",
|
"public": "公开",
|
||||||
"radius": "半径",
|
"radius": "半径",
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ export function createMarkerFromWaypoint(L: any, waypoint: Waypoint, onDragEnd?:
|
|||||||
const marker = L.marker([waypoint.lat, waypoint.lon], {
|
const marker = L.marker([waypoint.lat, waypoint.lon], {
|
||||||
title: waypoint.name,
|
title: waypoint.name,
|
||||||
icon: fontAwesomeIcon,
|
icon: fontAwesomeIcon,
|
||||||
draggable: onDragEnd != null
|
draggable: onDragEnd != null,
|
||||||
|
meta: {
|
||||||
|
waypointName: waypoint.name
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.bindPopup(
|
.bindPopup(
|
||||||
"<b>" +
|
"<b>" +
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const Colors = {
|
|||||||
'steelblue': { area: '#4682B4' },
|
'steelblue': { area: '#4682B4' },
|
||||||
'red' : { area: '#F00' },
|
'red' : { area: '#F00' },
|
||||||
'lime' : { area: '#9CC222', line: '#566B13' },
|
'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;
|
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),
|
minPoint = new L.Point(pixelBounds.min.x, pixelBounds.min.y),
|
||||||
pixelPoint = map.project(marker.getLatLng()),
|
pixelPoint = map.project(marker.getLatLng()),
|
||||||
url = "/vendor/leaflet-image/marker.png",
|
url = "/vendor/leaflet-image/marker.png",
|
||||||
icon = getComputedStyle(marker._icon.children[0], ':before').getPropertyValue('content').substring(1, 2) ?? "\uf111",
|
|
||||||
im = new Image(),
|
im = new Image(),
|
||||||
options = marker.options.icon.options,
|
options = marker.options.icon.options,
|
||||||
size = options.iconSize,
|
size = options.iconSize,
|
||||||
pos = pixelPoint.subtract(minPoint),
|
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];
|
if (size instanceof L.Point) size = [size.x, size.y];
|
||||||
|
|
||||||
@@ -214,10 +213,28 @@ export default function leafletImage(map, callback) {
|
|||||||
im.crossOrigin = '';
|
im.crossOrigin = '';
|
||||||
|
|
||||||
im.onload = function () {
|
im.onload = function () {
|
||||||
|
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.drawImage(this, x, y, size[0], size[1]);
|
||||||
ctx.font = '600 14px "Font Awesome 6 Free"';
|
ctx.font = '600 14px "Font Awesome 6 Free"';
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
ctx.fillText(icon, x + 10.2, y + 24);
|
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, {
|
callback(null, {
|
||||||
canvas: canvas
|
canvas: canvas
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,8 +2,15 @@
|
|||||||
import { page } from "$app/stores";
|
import { page } from "$app/stores";
|
||||||
import Button from "$lib/components/base/button.svelte";
|
import Button from "$lib/components/base/button.svelte";
|
||||||
import Select from "$lib/components/base/select.svelte";
|
import Select from "$lib/components/base/select.svelte";
|
||||||
|
import LogoText from "$lib/components/logo/logo_text.svelte";
|
||||||
import MapWithElevation from "$lib/components/trail/map_with_elevation.svelte";
|
import MapWithElevation from "$lib/components/trail/map_with_elevation.svelte";
|
||||||
import { trail } from "$lib/stores/trail_store";
|
import { trail } from "$lib/stores/trail_store";
|
||||||
|
import { currentUser } from "$lib/stores/user_store";
|
||||||
|
import {
|
||||||
|
formatDistance,
|
||||||
|
formatElevation,
|
||||||
|
formatTimeHHMM,
|
||||||
|
} from "$lib/util/format_util";
|
||||||
import {
|
import {
|
||||||
calculatePixelPerMeter,
|
calculatePixelPerMeter,
|
||||||
calculateScaleFactor,
|
calculateScaleFactor,
|
||||||
@@ -16,14 +23,7 @@
|
|||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import QrCodeWithLogo from "qrcode-with-logos";
|
import QrCodeWithLogo from "qrcode-with-logos";
|
||||||
import { onMount, tick } from "svelte";
|
import { onMount, tick } from "svelte";
|
||||||
import { currentUser } from "$lib/stores/user_store";
|
import { _ } from "svelte-i18n";
|
||||||
import {
|
|
||||||
formatDistance,
|
|
||||||
formatElevation,
|
|
||||||
formatTimeHHMM,
|
|
||||||
} from "$lib/util/format_util";
|
|
||||||
import LogoTextLight from "$lib/components/logo/logo_text_light.svelte";
|
|
||||||
import LogoText from "$lib/components/logo/logo_text.svelte";
|
|
||||||
|
|
||||||
let map: Map;
|
let map: Map;
|
||||||
|
|
||||||
@@ -164,9 +164,13 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="grid grid-cols-1 md:grid-cols-[400px_1fr] gap-x-1 gap-y-4">
|
<svelte:head>
|
||||||
|
<title>{$_("print")} | wanderer</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<main class="grid grid-cols-1 md:grid-cols-[400px_1fr] gap-x-1 gap-y-4 items-start">
|
||||||
<div
|
<div
|
||||||
class="print-details px-6 space-y-4 border border-input-border rounded-3xl"
|
class="print-details px-6 space-y-4 pb-4 border border-input-border rounded-3xl"
|
||||||
>
|
>
|
||||||
<div id="images"></div>
|
<div id="images"></div>
|
||||||
<h1 class="text-4xl font-bold">Print Trail</h1>
|
<h1 class="text-4xl font-bold">Print Trail</h1>
|
||||||
@@ -205,6 +209,7 @@
|
|||||||
legend: false,
|
legend: false,
|
||||||
time: false,
|
time: false,
|
||||||
hotline: false,
|
hotline: false,
|
||||||
|
graticule: true,
|
||||||
height: 150,
|
height: 150,
|
||||||
}}
|
}}
|
||||||
on:zoomend={(e) => updateScale(e.detail)}
|
on:zoomend={(e) => updateScale(e.detail)}
|
||||||
@@ -216,6 +221,24 @@
|
|||||||
alt="QR Code"
|
alt="QR Code"
|
||||||
/>
|
/>
|
||||||
<div class="basis-full mx-4">
|
<div class="basis-full mx-4">
|
||||||
|
<div
|
||||||
|
class="flex mt-1 gap-4 text-sm text-gray-500 whitespace-nowrap"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
><i class="fa fa-left-right mr-2"
|
||||||
|
></i>{formatDistance($trail.distance)}</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
><i class="fa fa-up-down mr-2"
|
||||||
|
></i>{formatElevation(
|
||||||
|
$trail.elevation_gain,
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
><i class="fa fa-clock mr-2"
|
||||||
|
></i>{formatTimeHHMM($trail.duration)}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
<svg width="100%" height="50" id="ruler"> </svg>
|
<svg width="100%" height="50" id="ruler"> </svg>
|
||||||
<span class="text-sm"
|
<span class="text-sm"
|
||||||
>Scale:   1 : {Math.round(scale)}</span
|
>Scale:   1 : {Math.round(scale)}</span
|
||||||
@@ -224,32 +247,13 @@
|
|||||||
</MapWithElevation>
|
</MapWithElevation>
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-4 -mt-4 border-t border-t-input-border z-10">
|
<div class="pt-4 -mt-4 border-t border-t-input-border z-10">
|
||||||
<div class="float-right"><LogoText></LogoText></div>
|
<div class="float-right"><LogoText height={48}></LogoText></div>
|
||||||
|
|
||||||
<h4 class="font-semibold">{$trail.name}</h4>
|
<h4 class="text-sm font-semibold">{$trail.name}</h4>
|
||||||
<h5>
|
<h5 class="text-sm">
|
||||||
<i class="fa fa-location-dot mr-2"></i>{$trail.location ||
|
<i class="fa fa-location-dot mr-2"></i>{$trail.location ||
|
||||||
"-"}
|
"-"}
|
||||||
</h5>
|
</h5>
|
||||||
<div
|
|
||||||
class="flex mt-1 gap-4 text-sm text-gray-500 whitespace-nowrap"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
><i class="fa fa-left-right mr-2"></i>{formatDistance(
|
|
||||||
$trail.distance,
|
|
||||||
)}</span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
><i class="fa fa-up-down mr-2"></i>{formatElevation(
|
|
||||||
$trail.elevation_gain,
|
|
||||||
)}</span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
><i class="fa fa-clock mr-2"></i>{formatTimeHHMM(
|
|
||||||
$trail.duration,
|
|
||||||
)}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user