more work on print view
This commit is contained in:
@@ -3,13 +3,16 @@
|
||||
import { currentUser } from "$lib/stores/user_store";
|
||||
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
|
||||
import type { Map, Marker } from "leaflet";
|
||||
import { onMount } from "svelte";
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
|
||||
export let trail: Trail;
|
||||
export let markers: Marker[] = [];
|
||||
export let map: Map | null = null;
|
||||
export let options = {};
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let L: any;
|
||||
let map: Map;
|
||||
let controlElevation: any;
|
||||
|
||||
$: if (trail.expand.gpx_data && controlElevation) {
|
||||
@@ -23,14 +26,21 @@
|
||||
//@ts-ignore
|
||||
await import("$lib/vendor/leaflet-elevation/src/index.js");
|
||||
|
||||
map = L.map("map").setView([trail.lat ?? 0, trail.lon ?? 0], 14);
|
||||
map.attributionControl.setPrefix(false)
|
||||
map = L.map("map", { preferCanvas: true }).setView(
|
||||
[trail.lat ?? 0, trail.lon ?? 0],
|
||||
14,
|
||||
);
|
||||
map!.attributionControl.setPrefix(false);
|
||||
|
||||
map!.on("zoomend", function () {
|
||||
dispatch("zoomend", map);
|
||||
});
|
||||
|
||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
attribution: "© OpenStreetMap contributors",
|
||||
}).addTo(map);
|
||||
|
||||
const elevation_options = {
|
||||
const default_elevation_options = {
|
||||
height: 200,
|
||||
|
||||
theme: "lightblue-theme",
|
||||
@@ -84,11 +94,16 @@
|
||||
},
|
||||
};
|
||||
|
||||
const elevation_options = Object.assign(
|
||||
default_elevation_options,
|
||||
options,
|
||||
);
|
||||
|
||||
controlElevation = L.control.elevation(elevation_options).addTo(map);
|
||||
|
||||
for (const waypoint of trail.expand.waypoints) {
|
||||
const marker = createMarkerFromWaypoint(L, waypoint);
|
||||
marker.addTo(map);
|
||||
marker.addTo(map!);
|
||||
markers.push(marker);
|
||||
}
|
||||
});
|
||||
@@ -96,16 +111,8 @@
|
||||
|
||||
<div id="map-container" class="flex flex-col">
|
||||
<div id="map" class="rounded-xl z-0 basis-full"></div>
|
||||
<div id="elevation"></div>
|
||||
<div class="flex items-center justify-between">
|
||||
<slot />
|
||||
<div class="basis-full min-w-[300px]" id="elevation"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#map-container {
|
||||
height: calc(100vh - 180px);
|
||||
}
|
||||
@media only screen and (min-width: 768px) {
|
||||
#map-container {
|
||||
height: calc(100vh - 124px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Waypoint } from "$lib/models/waypoint";
|
||||
import type { Icon, LeafletEvent, Marker } from "leaflet";
|
||||
import type { Icon, LeafletEvent, Map, Marker } from "leaflet";
|
||||
|
||||
export function createMarkerFromWaypoint(L: any, waypoint: Waypoint, onDragEnd?: (event: LeafletEvent) => void): Marker {
|
||||
const fontAwesomeIcon = L.AwesomeMarkers.icon({
|
||||
@@ -27,4 +27,43 @@ export function createMarkerFromWaypoint(L: any, waypoint: Waypoint, onDragEnd?:
|
||||
}
|
||||
|
||||
return marker;
|
||||
}
|
||||
|
||||
export function calculatePixelPerMeter(map: Map, meters: number) {
|
||||
const y = map.getSize().y;
|
||||
const x = map.getSize().x;
|
||||
const maxMeters = map.containerPointToLatLng([0, y]).distanceTo(map.containerPointToLatLng([x, y]));
|
||||
const pixelPerMeter = x / maxMeters;
|
||||
|
||||
return pixelPerMeter * meters
|
||||
}
|
||||
|
||||
export function calculateScaleFactor(map: Map) {
|
||||
function _pxTOmm() {
|
||||
let heightRef = document.createElement('div');
|
||||
heightRef.style.height = '1mm';
|
||||
heightRef.style.position = "absolute";
|
||||
heightRef.id = 'heightRef';
|
||||
document.body.appendChild(heightRef);
|
||||
|
||||
const pxPermm = heightRef.getBoundingClientRect().height;
|
||||
|
||||
document.body.removeChild(heightRef);
|
||||
|
||||
return function pxTOmm(px: number) {
|
||||
return px / pxPermm;
|
||||
}
|
||||
}
|
||||
var centerOfMap = map.getSize().y / 2;
|
||||
|
||||
var realWorlMetersPer100Pixels = map.distance(
|
||||
map.containerPointToLatLng([0, centerOfMap]),
|
||||
map.containerPointToLatLng([100, centerOfMap])
|
||||
);
|
||||
|
||||
const screenMetersPer100Pixels = _pxTOmm()(100) / 1000;
|
||||
|
||||
const scaleFactor = realWorlMetersPer100Pixels / screenMetersPer100Pixels
|
||||
|
||||
return scaleFactor
|
||||
}
|
||||
29
web/src/lib/util/svg_util.ts
Normal file
29
web/src/lib/util/svg_util.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export function createRect(
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
fill: string,
|
||||
) {
|
||||
const svgns = "http://www.w3.org/2000/svg";
|
||||
|
||||
const rect = document.createElementNS(svgns, "rect");
|
||||
rect.setAttribute("x", x.toString());
|
||||
rect.setAttribute("y", y.toString());
|
||||
rect.setAttribute("width", width.toString());
|
||||
rect.setAttribute("height", height.toString());
|
||||
rect.setAttribute("fill", fill);
|
||||
return rect;
|
||||
}
|
||||
|
||||
export function createText(content: string, x: number,
|
||||
y: number, fontSize?: string) {
|
||||
const svgns = "http://www.w3.org/2000/svg";
|
||||
|
||||
const text = document.createElementNS(svgns, "text");
|
||||
text.setAttribute("x", x.toString());
|
||||
text.setAttribute("y", y.toString());
|
||||
text.style.fontSize = fontSize ?? ""
|
||||
text.textContent = content;
|
||||
return text;
|
||||
}
|
||||
@@ -773,6 +773,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
||||
this.track_info.name = geojson.name;
|
||||
}
|
||||
|
||||
style.color = "#ff0000"
|
||||
let layer = L.geoJson(geojson, {
|
||||
distanceMarkers: distanceMarkers,
|
||||
style: style,
|
||||
|
||||
@@ -319,6 +319,17 @@
|
||||
--ele-grid: rgba(var(--input-border));
|
||||
}
|
||||
|
||||
/* GRAY THEME //////////////////////////////////////////////// */
|
||||
.gray-theme {
|
||||
--ele-bg: #ffffff;
|
||||
--ele-area: #242734;
|
||||
--ele-alpha: 0.45;
|
||||
--ele-stroke: #7b7b7b;
|
||||
--ele-circle: #fff;
|
||||
--ele-line: #000;
|
||||
--ele-grid: rgba(var(--input-border));
|
||||
}
|
||||
|
||||
.elevation-detached.lightblue-theme .area {
|
||||
stroke: #3366CC;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ export const Colors = {
|
||||
'purple' : { area: '#732C7B' },
|
||||
'steelblue': { area: '#4682B4' },
|
||||
'red' : { area: '#F00' },
|
||||
'lime' : { area: '#9CC222', line: '#566B13' }
|
||||
'lime' : { area: '#9CC222', line: '#566B13' },
|
||||
'gray': { area: '#000000', line: '#242734', alpha: 0.00001, stroke: '#000000' }
|
||||
};
|
||||
|
||||
const SEC = 1000;
|
||||
|
||||
261
web/src/lib/vendor/leaflet-image/leaflet-image.js
vendored
Normal file
261
web/src/lib/vendor/leaflet-image/leaflet-image.js
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
import queue from "./queue";
|
||||
|
||||
var cacheBusterDate = +new Date();
|
||||
|
||||
// leaflet-image
|
||||
export default function leafletImage(map, callback) {
|
||||
|
||||
var dimensions = map.getSize(),
|
||||
layerQueue = new queue(1);
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = dimensions.x;
|
||||
canvas.height = dimensions.y;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// dummy canvas image when loadTile get 404 error
|
||||
// and layer don't have errorTileUrl
|
||||
var dummycanvas = document.createElement('canvas');
|
||||
dummycanvas.width = 1;
|
||||
dummycanvas.height = 1;
|
||||
var dummyctx = dummycanvas.getContext('2d');
|
||||
dummyctx.fillStyle = 'rgba(0,0,0,0)';
|
||||
dummyctx.fillRect(0, 0, 1, 1);
|
||||
|
||||
// layers are drawn in the same order as they are composed in the DOM:
|
||||
// tiles, paths, and then markers
|
||||
map.eachLayer(drawTileLayer);
|
||||
map.eachLayer(drawEsriDynamicLayer);
|
||||
|
||||
if (map._pathRoot) {
|
||||
layerQueue.defer(handlePathRoot, map._pathRoot);
|
||||
} else if (map._panes) {
|
||||
var firstCanvas = map._panes.overlayPane.getElementsByTagName('canvas').item(0);
|
||||
if (firstCanvas) {
|
||||
layerQueue.defer(handlePathRoot, firstCanvas);
|
||||
}
|
||||
}
|
||||
map.eachLayer(drawMarkerLayer);
|
||||
layerQueue.awaitAll(layersDone);
|
||||
|
||||
function drawTileLayer(l) {
|
||||
if (l instanceof L.TileLayer) layerQueue.defer(handleTileLayer, l);
|
||||
else if (l._heat) layerQueue.defer(handlePathRoot, l._canvas);
|
||||
}
|
||||
|
||||
function drawMarkerLayer(l) {
|
||||
if (l instanceof L.Marker && l.options.icon instanceof L.Icon) {
|
||||
layerQueue.defer(handleMarkerLayer, l);
|
||||
}
|
||||
}
|
||||
|
||||
function drawEsriDynamicLayer(l) {
|
||||
if (!L.esri) return;
|
||||
|
||||
if (l instanceof L.esri.DynamicMapLayer) {
|
||||
layerQueue.defer(handleEsriDymamicLayer, l);
|
||||
}
|
||||
}
|
||||
|
||||
function done() {
|
||||
callback(null, canvas);
|
||||
}
|
||||
|
||||
function layersDone(err, layers) {
|
||||
if (err) throw err;
|
||||
layers.forEach(function (layer) {
|
||||
if (layer && layer.canvas) {
|
||||
ctx.drawImage(layer.canvas, 0, 0);
|
||||
}
|
||||
});
|
||||
done();
|
||||
}
|
||||
|
||||
function handleTileLayer(layer, callback) {
|
||||
// `L.TileLayer.Canvas` was removed in leaflet 1.0
|
||||
var isCanvasLayer = (L.TileLayer.Canvas && layer instanceof L.TileLayer.Canvas),
|
||||
canvas = document.createElement('canvas');
|
||||
|
||||
canvas.width = dimensions.x;
|
||||
canvas.height = dimensions.y;
|
||||
|
||||
var ctx = canvas.getContext('2d'),
|
||||
bounds = map.getPixelBounds(),
|
||||
zoom = map.getZoom(),
|
||||
tileSize = layer.options.tileSize;
|
||||
|
||||
if (zoom > layer.options.maxZoom ||
|
||||
zoom < layer.options.minZoom
|
||||
) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
var tileBounds = L.bounds(
|
||||
bounds.min.divideBy(tileSize)._floor(),
|
||||
bounds.max.divideBy(tileSize)._floor()),
|
||||
tiles = [],
|
||||
j, i,
|
||||
tileQueue = new queue(1);
|
||||
|
||||
for (j = tileBounds.min.y; j <= tileBounds.max.y; j++) {
|
||||
for (i = tileBounds.min.x; i <= tileBounds.max.x; i++) {
|
||||
tiles.push(new L.Point(i, j));
|
||||
}
|
||||
}
|
||||
|
||||
tiles.forEach(function (tilePoint) {
|
||||
var originalTilePoint = tilePoint.clone();
|
||||
|
||||
if (layer._adjustTilePoint) {
|
||||
layer._adjustTilePoint(tilePoint);
|
||||
}
|
||||
|
||||
var tilePos = originalTilePoint
|
||||
.scaleBy(new L.Point(tileSize, tileSize))
|
||||
.subtract(bounds.min);
|
||||
|
||||
if (tilePoint.y >= 0) {
|
||||
if (isCanvasLayer) {
|
||||
var tile = layer._tiles[tilePoint.x + ':' + tilePoint.y];
|
||||
tileQueue.defer(canvasTile, tile, tilePos, tileSize);
|
||||
} else {
|
||||
var url = addCacheString(layer.getTileUrl(tilePoint));
|
||||
tileQueue.defer(loadTile, url, tilePos, tileSize);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tileQueue.awaitAll(tileQueueFinish);
|
||||
|
||||
function canvasTile(tile, tilePos, tileSize, callback) {
|
||||
callback(null, {
|
||||
img: tile,
|
||||
pos: tilePos,
|
||||
size: tileSize
|
||||
});
|
||||
}
|
||||
|
||||
function loadTile(url, tilePos, tileSize, callback) {
|
||||
var im = new Image();
|
||||
im.crossOrigin = '';
|
||||
im.onload = function () {
|
||||
callback(null, {
|
||||
img: this,
|
||||
pos: tilePos,
|
||||
size: tileSize
|
||||
});
|
||||
};
|
||||
im.onerror = function (e) {
|
||||
// use canvas instead of errorTileUrl if errorTileUrl get 404
|
||||
if (layer.options.errorTileUrl != '' && e.target.errorCheck === undefined) {
|
||||
e.target.errorCheck = true;
|
||||
e.target.src = layer.options.errorTileUrl;
|
||||
} else {
|
||||
callback(null, {
|
||||
img: dummycanvas,
|
||||
pos: tilePos,
|
||||
size: tileSize
|
||||
});
|
||||
}
|
||||
};
|
||||
im.src = url;
|
||||
}
|
||||
|
||||
function tileQueueFinish(err, data) {
|
||||
data.forEach(drawTile);
|
||||
callback(null, { canvas: canvas });
|
||||
}
|
||||
|
||||
function drawTile(d) {
|
||||
ctx.drawImage(d.img, Math.floor(d.pos.x), Math.floor(d.pos.y),
|
||||
d.size, d.size);
|
||||
}
|
||||
}
|
||||
|
||||
function handlePathRoot(root, callback) {
|
||||
var bounds = map.getPixelBounds(),
|
||||
origin = map.getPixelOrigin(),
|
||||
canvas = document.createElement('canvas');
|
||||
canvas.width = dimensions.x;
|
||||
canvas.height = dimensions.y;
|
||||
var ctx = canvas.getContext('2d');
|
||||
var pos = L.DomUtil.getPosition(root).subtract(bounds.min).add(origin);
|
||||
try {
|
||||
ctx.drawImage(root, pos.x, pos.y, canvas.width - (pos.x * 2), canvas.height - (pos.y * 2));
|
||||
callback(null, {
|
||||
canvas: canvas
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Element could not be drawn on canvas', root); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
|
||||
function handleMarkerLayer(marker, callback) {
|
||||
var canvas = document.createElement('canvas'),
|
||||
ctx = canvas.getContext('2d'),
|
||||
pixelBounds = map.getPixelBounds(),
|
||||
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));
|
||||
|
||||
if (size instanceof L.Point) size = [size.x, size.y];
|
||||
|
||||
var x = Math.round(pos.x - size[0] + anchor.x),
|
||||
y = Math.round(pos.y - anchor.y);
|
||||
|
||||
canvas.width = dimensions.x;
|
||||
canvas.height = dimensions.y;
|
||||
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);
|
||||
callback(null, {
|
||||
canvas: canvas
|
||||
});
|
||||
};
|
||||
|
||||
im.src = url;
|
||||
}
|
||||
|
||||
function handleEsriDymamicLayer(dynamicLayer, callback) {
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = dimensions.x;
|
||||
canvas.height = dimensions.y;
|
||||
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
var im = new Image();
|
||||
im.crossOrigin = '';
|
||||
im.src = addCacheString(dynamicLayer._currentImage._image.src);
|
||||
|
||||
im.onload = function () {
|
||||
ctx.drawImage(im, 0, 0);
|
||||
callback(null, {
|
||||
canvas: canvas
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function addCacheString(url) {
|
||||
// If it's a data URL we don't want to touch this.
|
||||
if (isDataURL(url)) {
|
||||
return url;
|
||||
}
|
||||
return url + ((url.match(/\?/)) ? '&' : '?') + 'cache=' + cacheBusterDate;
|
||||
}
|
||||
|
||||
function isDataURL(url) {
|
||||
var dataURLRegex = /^\s*data:([a-z]+\/[a-z]+(;[a-z\-]+\=[a-z\-]+)?)?(;base64)?,[a-z0-9\!\$\&\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i;
|
||||
return !!url.match(dataURLRegex);
|
||||
}
|
||||
|
||||
};
|
||||
120
web/src/lib/vendor/leaflet-image/queue.js
vendored
Normal file
120
web/src/lib/vendor/leaflet-image/queue.js
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
var noabort = {};
|
||||
const slice = [].slice;
|
||||
|
||||
function Queue(size) {
|
||||
this._size = size;
|
||||
this._call =
|
||||
this._error = null;
|
||||
this._tasks = [];
|
||||
this._data = [];
|
||||
this._waiting =
|
||||
this._active =
|
||||
this._ended =
|
||||
this._start = 0; // inside a synchronous task callback?
|
||||
}
|
||||
|
||||
Queue.prototype = queue.prototype = {
|
||||
constructor: Queue,
|
||||
defer: function(callback) {
|
||||
if (typeof callback !== "function") throw new Error("invalid callback");
|
||||
if (this._call) throw new Error("defer after await");
|
||||
if (this._error != null) return this;
|
||||
var t = slice.call(arguments, 1);
|
||||
t.push(callback);
|
||||
++this._waiting, this._tasks.push(t);
|
||||
poke(this);
|
||||
return this;
|
||||
},
|
||||
abort: function() {
|
||||
if (this._error == null) abort(this, new Error("abort"));
|
||||
return this;
|
||||
},
|
||||
await: function(callback) {
|
||||
if (typeof callback !== "function") throw new Error("invalid callback");
|
||||
if (this._call) throw new Error("multiple await");
|
||||
this._call = function(error, results) { callback.apply(null, [error].concat(results)); };
|
||||
maybeNotify(this);
|
||||
return this;
|
||||
},
|
||||
awaitAll: function(callback) {
|
||||
if (typeof callback !== "function") throw new Error("invalid callback");
|
||||
if (this._call) throw new Error("multiple await");
|
||||
this._call = callback;
|
||||
maybeNotify(this);
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
function poke(q) {
|
||||
if (!q._start) {
|
||||
try { start(q); } // let the current task complete
|
||||
catch (e) {
|
||||
if (q._tasks[q._ended + q._active - 1]) abort(q, e); // task errored synchronously
|
||||
else if (!q._data) throw e; // await callback errored synchronously
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function start(q) {
|
||||
while (q._start = q._waiting && q._active < q._size) {
|
||||
var i = q._ended + q._active,
|
||||
t = q._tasks[i],
|
||||
j = t.length - 1,
|
||||
c = t[j];
|
||||
t[j] = end(q, i);
|
||||
--q._waiting, ++q._active;
|
||||
t = c.apply(null, t);
|
||||
if (!q._tasks[i]) continue; // task finished synchronously
|
||||
q._tasks[i] = t || noabort;
|
||||
}
|
||||
}
|
||||
|
||||
function end(q, i) {
|
||||
return function(e, r) {
|
||||
if (!q._tasks[i]) return; // ignore multiple callbacks
|
||||
--q._active, ++q._ended;
|
||||
q._tasks[i] = null;
|
||||
if (q._error != null) return; // ignore secondary errors
|
||||
if (e != null) {
|
||||
abort(q, e);
|
||||
} else {
|
||||
q._data[i] = r;
|
||||
if (q._waiting) poke(q);
|
||||
else maybeNotify(q);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function abort(q, e) {
|
||||
var i = q._tasks.length, t;
|
||||
q._error = e; // ignore active callbacks
|
||||
q._data = undefined; // allow gc
|
||||
q._waiting = NaN; // prevent starting
|
||||
|
||||
while (--i >= 0) {
|
||||
if (t = q._tasks[i]) {
|
||||
q._tasks[i] = null;
|
||||
if (t.abort) {
|
||||
try { t.abort(); }
|
||||
catch (e) { /* ignore */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
q._active = NaN; // allow notification
|
||||
maybeNotify(q);
|
||||
}
|
||||
|
||||
function maybeNotify(q) {
|
||||
if (!q._active && q._call) {
|
||||
var d = q._data;
|
||||
q._data = undefined; // allow gc
|
||||
q._call(q._error, d);
|
||||
}
|
||||
}
|
||||
|
||||
export default function queue(concurrency) {
|
||||
if (concurrency == null) concurrency = Infinity;
|
||||
else if (!((concurrency = +concurrency) >= 1)) throw new Error("invalid concurrency");
|
||||
return new Queue(concurrency);
|
||||
}
|
||||
Reference in New Issue
Block a user