Implement map

This commit is contained in:
2022-09-11 23:16:22 +02:00
parent 8651f60645
commit c63eeb83a0
16 changed files with 1035 additions and 46 deletions

View File

@@ -5,6 +5,10 @@
import { user } from "./stores";
import Navbar from "./Navbar.svelte";
import P404 from "./pages/P404.svelte";
import Mining from "./pages/mining/Mining.svelte";
import Footer from "./Footer.svelte";
import BaseLayout from "./BaseLayout.svelte";
onMount(async () => {
const res = await fetch("/user/me");
@@ -13,16 +17,16 @@
</script>
<Router>
<Navbar />
<div class="is-flex-grow-1">
<Route path="/">
<Route path="/">
<BaseLayout>
<section class="section">
<p>Hallo i bims 1 frontend</p>
</section>
</Route>
</BaseLayout>
</Route>
<Route path="/foo">
<Route path="/foo">
<BaseLayout>
<section class="hero is-danger is-fullheight">
<div class="hero-body">
<div class="">
@@ -31,35 +35,26 @@
</div>
</div>
</section>
</Route>
</BaseLayout>
</Route>
<Route path="/bar">bar</Route>
<Route path="/bar">
<BaseLayout>bar</BaseLayout>
</Route>
<Route path="/monitoring">monitoring</Route>
<Route path="/monitoring">
<BaseLayout>monitoring</BaseLayout>
</Route>
<Route path="/mining">mining</Route>
<Route path="/mining">
<Mining />
</Route>
<Route path="/stats">stats</Route>
<Route path="/stats">
<BaseLayout>stats</BaseLayout>
</Route>
<Route>
<section class="section">
<div class="container has-text-centered">
<h1 class="title is-1">404</h1>
<h2 class="subtitle is-5">
The page you are trying to reach either
<strong>does not exist</strong>
or
<strong>you are not authorized</strong>
to view it.
</h2>
</div>
</section>
</Route>
</div>
<footer class="footer">
<div class="has-text-centered">
Test footer please ignore
</div>
</footer>
<Route>
<BaseLayout><P404 /></BaseLayout>
</Route>
</Router>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import Footer from "./Footer.svelte";
import Navbar from "./Navbar.svelte";
</script>
<div class="main-container">
<Navbar />
<div class="is-flex-grow-1">
<slot />
</div>
<Footer />
</div>
<style>
.main-container {
display: flex;
flex-direction: column;
min-height: 100vh;
}
</style>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import { useLocation } from "svelte-navigator";
const location = useLocation();
</script>
{#if $location.pathname !== "/mining"}
<footer class="footer">
<div class="has-text-centered">
<p>Test footer please ignore</p>
<p>location: {$location.pathname}</p>
</div>
</footer>
{/if}

View File

@@ -17,12 +17,6 @@ $fa-font-path: "@fortawesome/fontawesome-free/webfonts";
@import "@fortawesome/fontawesome-free/scss/brands.scss";
@import "@fortawesome/fontawesome-free/scss/v4-shims.scss";
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
// https://github.com/mefechoel/svelte-navigator#what-are-the-weird-rectangles-around-the-headings-in-my-app
h1:focus {
outline: none;

View File

@@ -1,6 +1,8 @@
import 'vite/modulepreload-polyfill'
import "ol/ol.css";
import "./app.scss";
import App from "./App.svelte";
const app = new App({

View File

@@ -0,0 +1,12 @@
<section class="section">
<div class="container has-text-centered">
<h1 class="title is-1">404</h1>
<h2 class="subtitle is-5">
The page you are trying to reach either
<strong>does not exist</strong>
or
<strong>you are not authorized</strong>
to view it.
</h2>
</div>
</section>

View File

@@ -0,0 +1,71 @@
<script lang="ts">
import Navbar from "../../Navbar.svelte";
import {
Unmined,
type UnminedOptions,
type UnminedRegions,
} from "./unmined";
export let id = "map";
let unmined = new Unmined();
type Metadata = {
properties: UnminedOptions;
regions: UnminedRegions;
};
async function get_metadata() {
let resp = await fetch("map/metadata.js");
let code = await resp.text();
let meta = Function(code).call({}); // TODO possibly vulnerable to prototype pollution
return meta;
}
function setupMap(node: HTMLDivElement, metadata: Metadata) {
unmined.map(node.id, metadata.properties, metadata.regions);
return {
destroy() {
if (unmined.openlayersMap) {
unmined.openlayersMap.setTarget(null);
unmined.openlayersMap = null;
}
},
};
}
</script>
<div class="map-outer">
<Navbar />
{#await get_metadata() then metadata}
<div class="map-target" {id} use:setupMap={metadata} />
{:catch err}
<div class="container">
<article class="message is-danger mt-5">
<div class="message-header">
<p>Error loading map</p>
</div>
<div class="message-body">
<p>Something went wrong:</p>
<figure>
<pre>{err}</pre>
</figure>
</div>
</article>
</div>
{/await}
</div>
<style>
.map-outer {
display: flex;
flex-direction: column;
height: 100vh;
}
.map-target {
flex-grow: 1;
background-color: #e2e2e2;
}
</style>

View File

@@ -0,0 +1,256 @@
import { boundingExtent } from "ol/extent";
import { addCoordinateTransforms, Projection, transform } from "ol/proj";
import TileGrid from "ol/tilegrid/TileGrid";
import { Tile } from "ol/layer";
import XYZ from "ol/source/XYZ";
import MousePosition from "ol/control/MousePosition";
import { createStringXY } from "ol/coordinate";
import OlMap from "ol/Map";
import { defaults as controlDefaults } from "ol/control/defaults";
import { Feature, View } from "ol";
import { Point } from "ol/geom";
import Style from "ol/style/Style";
import Icon from "ol/style/Icon";
import Text from "ol/style/Text";
import Fill from "ol/style/Fill";
import SourceVector from "ol/source/Vector";
import LayerVector from "ol/layer/Vector";
export type UnminedOptions = {
minZoom: number;
maxZoom: number;
defaultZoom: number;
imageFormat: string;
minRegionX: number;
minRegionZ: number;
maxRegionX: number;
maxRegionZ: number;
worldName: string;
background: string;
markers: Array<any>;
}
export type UnminedRegions = {}[];
export class Unmined {
openlayersMap: OlMap;
map(mapId: string, options: UnminedOptions, regions: any[]) {
const dpiScale = window.devicePixelRatio ?? 1.0;
const worldMinX = options.minRegionX * 512;
const worldMinY = options.minRegionZ * 512;
const worldWidth = (options.maxRegionX + 1 - options.minRegionX) * 512;
const worldHeight = (options.maxRegionZ + 1 - options.minRegionZ) * 512;
const worldTileSize = 256;
const worldMaxZoomFactor = Math.pow(2, options.maxZoom);
// left, bottom, right, top, Y is negated
var mapExtent = boundingExtent([
[worldMinX * worldMaxZoomFactor, -(worldMinY + worldHeight) * worldMaxZoomFactor],
[(worldMinX + worldWidth) * worldMaxZoomFactor, -worldMinY * worldMaxZoomFactor]]);
var viewProjection = new Projection({
code: 'VIEW',
units: 'pixels',
});
var dataProjection = new Projection({
code: 'DATA',
units: 'pixels',
});
// Coordinate transformation between view and data
// OpenLayers Y is positive up, world Y is positive down
addCoordinateTransforms(viewProjection, dataProjection,
function (coordinate) {
return [coordinate[0], -coordinate[1]];
},
function (coordinate) {
return [coordinate[0], -coordinate[1]];
});
const mapZoomLevels = options.maxZoom - options.minZoom;
// Resolution for each OpenLayers zoom level
var resolutions = new Array(mapZoomLevels + 1);
for (let z = 0; z < mapZoomLevels + 1; ++z) {
resolutions[mapZoomLevels - z] = Math.pow(2, z) * dpiScale / worldMaxZoomFactor;
}
var tileGrid = new TileGrid({
extent: mapExtent,
origin: [0, 0],
resolutions: resolutions,
tileSize: worldTileSize / dpiScale
});
var unminedLayer =
new Tile({
source: new XYZ({
projection: viewProjection,
tileGrid: tileGrid,
tilePixelRatio: dpiScale,
tileSize: worldTileSize / dpiScale,
tileUrlFunction: function (coordinate) {
const worldZoom = -(mapZoomLevels - coordinate[0]) + options.maxZoom;
const worldZoomFactor = Math.pow(2, worldZoom);
const minTileX = Math.floor(worldMinX * worldZoomFactor / worldTileSize);
const minTileY = Math.floor(worldMinY * worldZoomFactor / worldTileSize);
const maxTileX = Math.ceil((worldMinX + worldWidth) * worldZoomFactor / worldTileSize) - 1;
const maxTileY = Math.ceil((worldMinY + worldHeight) * worldZoomFactor / worldTileSize) - 1;
const tileX = coordinate[1];
const tileY = coordinate[2];
const tileBlockSize = worldTileSize / worldZoomFactor;
const tileBlockPoint = {
x: tileX * tileBlockSize,
z: tileY * tileBlockSize
};
const hasTile = function () {
const tileRegionPoint = {
x: Math.floor(tileBlockPoint.x / 512),
z: Math.floor(tileBlockPoint.z / 512)
};
const tileRegionSize = Math.ceil(tileBlockSize / 512);
for (let x = tileRegionPoint.x; x < tileRegionPoint.x + tileRegionSize; x++) {
for (let z = tileRegionPoint.z; z < tileRegionPoint.z + tileRegionSize; z++) {
const group = {
x: Math.floor(x / 32),
z: Math.floor(z / 32)
};
const regionMap = regions.find(e => e.x == group.x && e.z == group.z);
if (regionMap) {
const relX = x - group.x * 32;
const relZ = z - group.z * 32;
const inx = relZ * 32 + relX;
var b = regionMap.m[Math.floor(inx / 32)];
var bit = inx % 32;
var found = (b & (1 << bit)) != 0;
if (found) return true;
}
}
}
return false;
};
if (tileX >= minTileX
&& tileY >= minTileY
&& tileX <= maxTileX
&& tileY <= maxTileY
&& hasTile()) {
const z = worldZoom,
yd = Math.floor(tileY / 10),
xd = Math.floor(tileX / 10),
y = tileY,
x = tileX
const url = `map/tiles/zoom.${z}/${xd}/${yd}/tile.${x}.${y}.${options.imageFormat}`
return url;
}
else
return undefined;
}
})
});
var mousePositionControl = new MousePosition({
coordinateFormat: createStringXY(0),
projection: dataProjection
});
var map = new OlMap({
target: mapId,
controls: controlDefaults().extend([
mousePositionControl
]),
layers: [
unminedLayer,
/*
new ol.layer.Tile({
source: new ol.source.TileDebug({
tileGrid: unminedTileGrid,
projection: viewProjection
})
})
*/
],
view: new View({
center: [0, 0],
extent: mapExtent,
projection: viewProjection,
resolutions: tileGrid.getResolutions(),
maxZoom: mapZoomLevels,
zoom: mapZoomLevels - options.maxZoom,
constrainResolution: true,
showFullExtent: true,
constrainOnlyCenter: true
})
});
if (options.markers) {
var markersLayer = this.createMarkersLayer(options.markers, dataProjection, viewProjection);
map.addLayer(markersLayer);
}
if (options.background) {
document.getElementById(mapId).style.backgroundColor = options.background;
}
this.openlayersMap = map;
}
createMarkersLayer(markers, dataProjection, viewProjection) {
var features = [];
for (var i = 0; i < markers.length; i++) {
var item = markers[i];
var longitude = item.x;
var latitude = item.z;
var feature = new Feature({
geometry: new Point(transform([longitude, latitude], dataProjection, viewProjection))
});
var style = new Style();
if (item.image)
style.setImage(new Icon({
src: item.image,
anchor: item.imageAnchor,
scale: item.imageScale
}));
if (item.text)
style.setText(new Text({
text: item.text,
font: item.font,
offsetX: item.offsetX,
offsetY: item.offsetY,
fill: new Fill({
color: item.textColor
})
}));
feature.setStyle(style);
features.push(feature);
}
var vectorSource = new SourceVector({
features: features
});
var vectorLayer = new LayerVector({
source: vectorSource
});
return vectorLayer;
}
}