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",
|
||||
"meilisearch": "^0.37.0",
|
||||
"nouislider": "^15.7.1",
|
||||
"pdfkit": "^0.15.0",
|
||||
"photoswipe": "^5.4.3",
|
||||
"pocketbase": "^0.21.0",
|
||||
"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");
|
||||
//@ts-ignore
|
||||
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(
|
||||
[trail.lat ?? 0, trail.lon ?? 0],
|
||||
@@ -92,6 +93,7 @@
|
||||
iconColor: "white",
|
||||
}),
|
||||
},
|
||||
graticule: false
|
||||
};
|
||||
|
||||
const elevation_options = Object.assign(
|
||||
@@ -106,6 +108,11 @@
|
||||
marker.addTo(map!);
|
||||
markers.push(marker);
|
||||
}
|
||||
|
||||
if(elevation_options.graticule) {
|
||||
new AutoGraticule().addTo(map!);
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -83,6 +83,11 @@
|
||||
"metric": "Metrisch",
|
||||
"moderate": "Mittel",
|
||||
"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": "",
|
||||
"name": "Name",
|
||||
"near": "Nahe",
|
||||
@@ -99,6 +104,7 @@
|
||||
"pick-a-trail": "Route auswählen",
|
||||
"polish": "Polnisch",
|
||||
"portuguese": "Portugiesisch",
|
||||
"print": "",
|
||||
"profile": "Profil",
|
||||
"public": "Öffentlich",
|
||||
"radius": "Radius",
|
||||
|
||||
@@ -83,12 +83,12 @@
|
||||
"metric": "Metric",
|
||||
"moderate": "Moderate",
|
||||
"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-hours-ago": "{n} hours ago",
|
||||
"n-minutes-ago": "{n} minutes ago",
|
||||
"n-months-ago": "{n} months ago",
|
||||
"n-seconds-ago": "{n} seconds ago",
|
||||
"n-years-ago": "{n} years ago",
|
||||
"name": "Name",
|
||||
"near": "Near",
|
||||
"new-list": "New List",
|
||||
@@ -104,6 +104,7 @@
|
||||
"pick-a-trail": "Pick a trail",
|
||||
"polish": "Polish",
|
||||
"portuguese": "Portuguese",
|
||||
"print": "Print",
|
||||
"profile": "Profile",
|
||||
"public": "Public",
|
||||
"radius": "Radius",
|
||||
|
||||
@@ -83,6 +83,11 @@
|
||||
"metric": "Métrique",
|
||||
"moderate": "Modérer",
|
||||
"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": "",
|
||||
"name": "Nom",
|
||||
"near": "Proche",
|
||||
@@ -99,6 +104,7 @@
|
||||
"pick-a-trail": "Choisir un itinéraire",
|
||||
"polish": "polonais",
|
||||
"portuguese": "Portugais",
|
||||
"print": "",
|
||||
"profile": "Profile",
|
||||
"public": "Publique",
|
||||
"radius": "Rayon",
|
||||
|
||||
@@ -83,6 +83,11 @@
|
||||
"metric": "Metrikus",
|
||||
"moderate": "Mérsékelt",
|
||||
"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": "",
|
||||
"name": "Név",
|
||||
"near": "Közelben",
|
||||
@@ -99,6 +104,7 @@
|
||||
"pick-a-trail": "Válasszon útvonalat",
|
||||
"polish": "Lengyel",
|
||||
"portuguese": "Portugál",
|
||||
"print": "",
|
||||
"profile": "Profil",
|
||||
"public": "Publikus",
|
||||
"radius": "Átmérő",
|
||||
|
||||
@@ -83,6 +83,11 @@
|
||||
"metric": "Metrisch",
|
||||
"moderate": "Gemiddeld",
|
||||
"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": "",
|
||||
"name": "Naam",
|
||||
"near": "Nabij",
|
||||
@@ -99,6 +104,7 @@
|
||||
"pick-a-trail": "Kies een wandelroute",
|
||||
"polish": "Pools",
|
||||
"portuguese": "Portugees",
|
||||
"print": "",
|
||||
"profile": "Profiel",
|
||||
"public": "Openbaar",
|
||||
"radius": "Straal",
|
||||
|
||||
@@ -83,6 +83,11 @@
|
||||
"metric": "Metryczne",
|
||||
"moderate": "Średnia",
|
||||
"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": "",
|
||||
"name": "Nazwa",
|
||||
"near": "Blisko",
|
||||
@@ -99,6 +104,7 @@
|
||||
"pick-a-trail": "Wybierz ścieżkę",
|
||||
"polish": "Polski",
|
||||
"portuguese": "Portugalski",
|
||||
"print": "",
|
||||
"profile": "Profil",
|
||||
"public": "Publiczny",
|
||||
"radius": "Promień",
|
||||
|
||||
@@ -83,6 +83,11 @@
|
||||
"metric": "Métrica",
|
||||
"moderate": "Moderado",
|
||||
"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": "",
|
||||
"name": "Nome",
|
||||
"near": "Próximo",
|
||||
@@ -99,6 +104,7 @@
|
||||
"pick-a-trail": "Escolha uma trilha",
|
||||
"polish": "Polonês",
|
||||
"portuguese": "Português",
|
||||
"print": "",
|
||||
"profile": "Perfil",
|
||||
"public": "Público",
|
||||
"radius": "Raio",
|
||||
|
||||
@@ -83,6 +83,11 @@
|
||||
"metric": "公制",
|
||||
"moderate": "中等",
|
||||
"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": "",
|
||||
"name": "名称",
|
||||
"near": "附近",
|
||||
@@ -99,6 +104,7 @@
|
||||
"pick-a-trail": "选择一个路线",
|
||||
"polish": "波兰语",
|
||||
"portuguese": "葡萄牙语",
|
||||
"print": "",
|
||||
"profile": "个人资料",
|
||||
"public": "公开",
|
||||
"radius": "半径",
|
||||
|
||||
@@ -12,7 +12,10 @@ export function createMarkerFromWaypoint(L: any, waypoint: Waypoint, onDragEnd?:
|
||||
const marker = L.marker([waypoint.lat, waypoint.lon], {
|
||||
title: waypoint.name,
|
||||
icon: fontAwesomeIcon,
|
||||
draggable: onDragEnd != null
|
||||
draggable: onDragEnd != null,
|
||||
meta: {
|
||||
waypointName: waypoint.name
|
||||
}
|
||||
})
|
||||
.bindPopup(
|
||||
"<b>" +
|
||||
|
||||
@@ -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 () {
|
||||
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
|
||||
});
|
||||
|
||||
@@ -2,8 +2,15 @@
|
||||
import { page } from "$app/stores";
|
||||
import Button from "$lib/components/base/button.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 { trail } from "$lib/stores/trail_store";
|
||||
import { currentUser } from "$lib/stores/user_store";
|
||||
import {
|
||||
formatDistance,
|
||||
formatElevation,
|
||||
formatTimeHHMM,
|
||||
} from "$lib/util/format_util";
|
||||
import {
|
||||
calculatePixelPerMeter,
|
||||
calculateScaleFactor,
|
||||
@@ -16,14 +23,7 @@
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import QrCodeWithLogo from "qrcode-with-logos";
|
||||
import { onMount, tick } from "svelte";
|
||||
import { currentUser } from "$lib/stores/user_store";
|
||||
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";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
let map: Map;
|
||||
|
||||
@@ -164,9 +164,13 @@
|
||||
}
|
||||
</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
|
||||
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>
|
||||
<h1 class="text-4xl font-bold">Print Trail</h1>
|
||||
@@ -205,6 +209,7 @@
|
||||
legend: false,
|
||||
time: false,
|
||||
hotline: false,
|
||||
graticule: true,
|
||||
height: 150,
|
||||
}}
|
||||
on:zoomend={(e) => updateScale(e.detail)}
|
||||
@@ -216,6 +221,24 @@
|
||||
alt="QR Code"
|
||||
/>
|
||||
<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>
|
||||
<span class="text-sm"
|
||||
>Scale:   1 : {Math.round(scale)}</span
|
||||
@@ -224,32 +247,13 @@
|
||||
</MapWithElevation>
|
||||
</div>
|
||||
<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>
|
||||
<h5>
|
||||
<h4 class="text-sm font-semibold">{$trail.name}</h4>
|
||||
<h5 class="text-sm">
|
||||
<i class="fa fa-location-dot mr-2"></i>{$trail.location ||
|
||||
"-"}
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user