Implement map
This commit is contained in:
@@ -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>
|
||||
|
||||
20
frontend/src/BaseLayout.svelte
Normal file
20
frontend/src/BaseLayout.svelte
Normal 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>
|
||||
13
frontend/src/Footer.svelte
Normal file
13
frontend/src/Footer.svelte
Normal 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}
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'vite/modulepreload-polyfill'
|
||||
|
||||
import "ol/ol.css";
|
||||
import "./app.scss";
|
||||
|
||||
import App from "./App.svelte";
|
||||
|
||||
const app = new App({
|
||||
|
||||
12
frontend/src/pages/P404.svelte
Normal file
12
frontend/src/pages/P404.svelte
Normal 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>
|
||||
71
frontend/src/pages/mining/Mining.svelte
Normal file
71
frontend/src/pages/mining/Mining.svelte
Normal 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>
|
||||
256
frontend/src/pages/mining/unmined.ts
Normal file
256
frontend/src/pages/mining/unmined.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user