kopia lustrzana https://github.com/paraboul/mapchecking
Move to Vue composition API (and typescript)
rodzic
d766830206
commit
938dd2a764
|
@ -7,6 +7,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@googlemaps/js-api-loader": "^1.4.0",
|
"@googlemaps/js-api-loader": "^1.4.0",
|
||||||
|
"@types/google.maps": "^3.48.3",
|
||||||
"@vitejs/plugin-vue": "^2.2.4",
|
"@vitejs/plugin-vue": "^2.2.4",
|
||||||
"js-base64": "^3.5.2",
|
"js-base64": "^3.5.2",
|
||||||
"tailwindcss": "^3.0.23",
|
"tailwindcss": "^3.0.23",
|
||||||
|
|
84
src/App.vue
84
src/App.vue
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="md:flex flex-1 md:items-stretch md:flex-row">
|
<div class="md:flex flex-1 md:items-stretch md:flex-row">
|
||||||
<div class="h-[60%] md:h-full w-full">
|
<div class="h-[60%] md:h-full w-full">
|
||||||
<Map :density="density" :startHash="startHash" ref="map" @densityChange="densityUpdate" @hashChange="hashUpdate" @surfaceUpdate="surfaceUpdate" />
|
<Map :density="density" :startHash="startHash" ref="mapComponent" @densityChange="densityUpdate" @hashChange="hashUpdate" @surfaceUpdate="surfaceUpdate" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col relative w-full lg:w-2/3 py-2 md:px-4 font-sans md:border-l border-gray-500 bg-slate-100">
|
<div class="flex flex-col relative w-full lg:w-2/3 py-2 md:px-4 font-sans md:border-l border-gray-500 bg-slate-100">
|
||||||
<div class="order-last md:order-first px-4 mb-4 md:mb-0 md:px-0">
|
<div class="order-last md:order-first px-4 mb-4 md:mb-0 md:px-0">
|
||||||
|
@ -13,9 +13,9 @@
|
||||||
|
|
||||||
<div class="shadow-md md:rounded-md px-4 py-3 bg-white md:mt-4 mb-4 md:mb-1">
|
<div class="shadow-md md:rounded-md px-4 py-3 bg-white md:mt-4 mb-4 md:mb-1">
|
||||||
<div v-if="surface !== 0" class="relative">
|
<div v-if="surface !== 0" class="relative">
|
||||||
<span class="text-sm text-gray-700">Surface area <span class="font-semibold">{{ formatArea(surface) }}sqm</span> • <span class="font-semibold">{{ formatArea(surface_feet) }}sqft</span></span>
|
<span class="text-sm text-gray-700">Surface area <span class="font-semibold">{{ surface.toFixed(0) }}sqm</span> • <span class="font-semibold">{{ surface_feet.toFixed(0) }}sqft</span></span>
|
||||||
|
|
||||||
<button @click="$refs.map.reset()" class="rounded absolute right-0 px-2 py-1 text-xs inline-block bg-red-400 shadow-md text-white font-bold hover:shadow-none focus:outline-none">Reset the area</button>
|
<button @click="mapComponent.reset()" class="rounded absolute right-0 px-2 py-1 text-xs inline-block bg-red-400 shadow-md text-white font-bold hover:shadow-none focus:outline-none">Reset the area</button>
|
||||||
<div class="mt-2 space-y-2">
|
<div class="mt-2 space-y-2">
|
||||||
<span class="font-semibold">Crowd density <span class="text-xs text-gray-700"><a class="underline hover:no-underline" target="_blank" href="http://www.gkstill.com/Support/crowd-density/625sm/Density6.html">What does it look like?</a></span></span>
|
<span class="font-semibold">Crowd density <span class="text-xs text-gray-700"><a class="underline hover:no-underline" target="_blank" href="http://www.gkstill.com/Support/crowd-density/625sm/Density6.html">What does it look like?</a></span></span>
|
||||||
<input class="block w-full" type="range" min="0.1" max="5.0" step="0.05" v-model.number="density" />
|
<input class="block w-full" type="range" min="0.1" max="5.0" step="0.05" v-model.number="density" />
|
||||||
|
@ -47,9 +47,9 @@
|
||||||
<div class="shadow-md md:rounded-md px-4 py-3 bg-white md:mt-4 mb-4 md:mb-8">
|
<div class="shadow-md md:rounded-md px-4 py-3 bg-white md:mt-4 mb-4 md:mb-8">
|
||||||
<h2 class="font-bold mb-2">Examples</h2>
|
<h2 class="font-bold mb-2">Examples</h2>
|
||||||
|
|
||||||
<a href="javascript:void(0)" @click="$refs.map.reloadHash('bAAAgQJtzQ0LZXRJAAACQQVdzQ0K-UxJAlHNDQl5REkDTc0NCz1ESQP9zQ0KFUxJAHnRDQv5WEkA8dENC51oSQEF0Q0LRXhJAPnRDQldjEkA2dENCo2YSQA50Q0KPaxJA_HNDQq5uEkDac0NCV28SQJRzQ0IhcBJAYnNDQvFuEkAzc0NCEWoSQPNyQ0LIXxJAGnNDQsNZEkA')" class="inline-block btn rounded-md mr-3 mb-2 text-sm">Place du Trocadero - Paris</a>
|
<a href="javascript:void(0)" @click="mapComponent.reloadHash('bAAAgQJtzQ0LZXRJAAACQQVdzQ0K-UxJAlHNDQl5REkDTc0NCz1ESQP9zQ0KFUxJAHnRDQv5WEkA8dENC51oSQEF0Q0LRXhJAPnRDQldjEkA2dENCo2YSQA50Q0KPaxJA_HNDQq5uEkDac0NCV28SQJRzQ0IhcBJAYnNDQvFuEkAzc0NCEWoSQPNyQ0LIXxJAGnNDQsNZEkA')" class="inline-block btn rounded-md mr-3 mb-2 text-sm">Place du Trocadero - Paris</a>
|
||||||
<a href="javascript:void(0)" @click="$refs.map.reloadHash('bAAAAQEJ4Q0IdShdAAACQQcp4Q0IfKxdAeXlDQtI7F0CseENClVIXQNl3Q0IeaBdAG3dDQnlYF0A')" class="inline-block btn rounded-md mr-3 mb-2 text-sm">Place de la République</a>
|
<a href="javascript:void(0)" @click="mapComponent.reloadHash('bAAAAQEJ4Q0IdShdAAACQQcp4Q0IfKxdAeXlDQtI7F0CseENClVIXQNl3Q0IeaBdAG3dDQnlYF0A')" class="inline-block btn rounded-md mr-3 mb-2 text-sm">Place de la République</a>
|
||||||
<a href="javascript:void(0)" @click="$refs.map.reloadHash('bAAAAQHoRUkLzzlVBAABwQRsPUkISoVVB0A5SQhKhVUF_EFJChAhWQccQUkJBCFZB')" class="inline-block btn rounded-md mr-3 mb-2 text-sm">Tiergatern - Berlin</a>
|
<a href="javascript:void(0)" @click="mapComponent.reloadHash('bAAAAQHoRUkLzzlVBAABwQRsPUkISoVVB0A5SQhKhVUF_EFJChAhWQccQUkJBCFZB')" class="inline-block btn rounded-md mr-3 mb-2 text-sm">Tiergatern - Berlin</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="grow"></div>
|
<div class="grow"></div>
|
||||||
<div class="flex space-x-4 order-last bg-white p-4 text-xs tracking-tight md:-mx-4 items-center font-medium justify-center">
|
<div class="flex space-x-4 order-last bg-white p-4 text-xs tracking-tight md:-mx-4 items-center font-medium justify-center">
|
||||||
|
@ -61,57 +61,37 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import Map from './components/Map.vue'
|
import Map from './components/Map.vue'
|
||||||
import { tatween, Easing } from 'tatween';
|
import { tatween, Easing } from 'tatween';
|
||||||
|
import { computed, ref } from '@vue/reactivity';
|
||||||
|
|
||||||
export default {
|
const surface = ref(0);
|
||||||
name: 'App',
|
const density = ref(1.5);
|
||||||
components: {
|
const startHash = window.location.hash && window.location.hash.length > 3 ?
|
||||||
Map
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
surfaceUpdate(data) {
|
|
||||||
this.surface = data;
|
|
||||||
},
|
|
||||||
|
|
||||||
hashUpdate(hash) {
|
|
||||||
window.location.hash = hash;
|
|
||||||
},
|
|
||||||
|
|
||||||
densityUpdate(val) {
|
|
||||||
this.density = Math.round(val * 100) / 100;
|
|
||||||
},
|
|
||||||
|
|
||||||
formatArea(val) {
|
|
||||||
return Number.parseFloat(val).toFixed(0);
|
|
||||||
},
|
|
||||||
|
|
||||||
setDensity(val) {
|
|
||||||
tatween(800, Easing.Exponential.Out, (obj) => {
|
|
||||||
obj.density = val;
|
|
||||||
}, this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
surface: 0,
|
|
||||||
density: 1.5,
|
|
||||||
startHash: window.location.hash && window.location.hash.length > 3 ?
|
|
||||||
window.location.hash.substring(1) : ''
|
window.location.hash.substring(1) : ''
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
const mapComponent = ref();
|
||||||
surface_feet() {
|
|
||||||
return (this.surface * 10.764).toFixed(2);
|
|
||||||
},
|
|
||||||
|
|
||||||
estimated() {
|
const surfaceUpdate = (data: number) => {
|
||||||
return parseInt(this.surface * this.density);
|
surface.value = data;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hashUpdate = (hash: string) => {
|
||||||
|
window.location.hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
const densityUpdate = (val: number) => {
|
||||||
|
density.value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
const setDensity = (val: number) => {
|
||||||
|
tatween(800, Easing.Exponential.Out, (obj) => {
|
||||||
|
density.value = val;
|
||||||
|
}, density)
|
||||||
|
}
|
||||||
|
|
||||||
|
const surface_feet = computed(() => (surface.value * 10.764))
|
||||||
|
const estimated = computed(() => Math.round(surface.value * density.value))
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,259 +1,260 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full">
|
<div class="w-full h-full">
|
||||||
<input id="pac-input" class="controls" :class="[mapLoaded ? '' : 'hidden']" type="text" placeholder="Search Box">
|
<input ref="pacinput" class="controls" :class="[mapLoaded ? '' : 'hidden']" type="text" placeholder="Search Box">
|
||||||
<div class="w-full h-full" id="map"></div>
|
<div class="w-full h-full" id="map"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
import { Loader as MapLoader } from '@googlemaps/js-api-loader';
|
import { Loader as MapLoader } from '@googlemaps/js-api-loader';
|
||||||
import { Base64 } from 'js-base64'
|
import { Base64 } from 'js-base64'
|
||||||
|
import { onMounted, ref, watch, computed } from 'vue';
|
||||||
|
|
||||||
const loader = new MapLoader({
|
const loader = new MapLoader({
|
||||||
apiKey: "AIzaSyD7Vm3gm4Fm7jSkuIh_yM14GmYhz1P_S4M",
|
apiKey: "AIzaSyD7Vm3gm4Fm7jSkuIh_yM14GmYhz1P_S4M",
|
||||||
version: "weekly",
|
version: "3.48",
|
||||||
libraries: ["geometry", "places"]
|
libraries: ["geometry", "places"]
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
const props = defineProps<{
|
||||||
name: "Map",
|
density: number,
|
||||||
|
startHash: string
|
||||||
|
}>()
|
||||||
|
|
||||||
props: {
|
const emits = defineEmits<{
|
||||||
density: Number,
|
(event: "surfaceUpdate", val: number): void
|
||||||
startHash: String
|
(event: "densityChange", val: number): void
|
||||||
},
|
(event: "hashChange", val: string): void
|
||||||
|
}>()
|
||||||
|
|
||||||
mounted() {
|
const mapPosition = ref([48.862895, 2.286978, 18])
|
||||||
|
const arrPoly = ref<google.maps.LatLng[]>([])
|
||||||
|
const mapLoaded = ref(false);
|
||||||
|
const pacinput = ref()
|
||||||
|
|
||||||
loader.loadCallback(e => {
|
let currentMap : google.maps.Map | undefined;
|
||||||
if (e) {
|
let currentPolygon : google.maps.Polygon | undefined;
|
||||||
console.log(e);
|
let $updateHashTimer = null;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const map = new google.maps.Map(document.getElementById("map"), {
|
onMounted(() => {
|
||||||
zoom: this.mapPosition[2],
|
loader.loadCallback(e => {
|
||||||
center: {
|
if (e) {
|
||||||
lat: this.mapPosition[0],
|
console.log(e);
|
||||||
lng: this.mapPosition[1]
|
return;
|
||||||
},
|
}
|
||||||
mapTypeId: 'roadmap',
|
|
||||||
gestureHandling: 'greedy'
|
|
||||||
});
|
|
||||||
|
|
||||||
const input = document.getElementById('pac-input');
|
currentMap = new google.maps.Map(document.getElementById("map"), {
|
||||||
const searchBox = new google.maps.places.SearchBox(input);
|
zoom: mapPosition.value[2],
|
||||||
map.controls[google.maps.ControlPosition.LEFT_TOP].push(input);
|
center: {
|
||||||
|
lat: mapPosition.value[0],
|
||||||
searchBox.addListener('places_changed', () => {
|
lng: mapPosition.value[1]
|
||||||
var places = searchBox.getPlaces();
|
},
|
||||||
|
mapTypeId: 'roadmap',
|
||||||
if (places.length == 0) {
|
gestureHandling: 'greedy'
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var place = places[0];
|
|
||||||
|
|
||||||
map.setCenter(place.geometry.location);
|
|
||||||
map.setZoom(17);
|
|
||||||
|
|
||||||
this.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
map.addListener('bounds_changed', function() {
|
|
||||||
searchBox.setBounds(map.getBounds());
|
|
||||||
});
|
|
||||||
map.addListener('center_changed', this.mapUpdated);
|
|
||||||
map.addListener('zoom_changed', this.mapUpdated);
|
|
||||||
map.addListener('click', this.mapClicked);
|
|
||||||
|
|
||||||
map.setOptions({
|
|
||||||
draggableCursor:'crosshair',
|
|
||||||
clickableIcons: false,
|
|
||||||
disableDoubleClickZoom: true,
|
|
||||||
streetViewControl: false
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$map = map;
|
|
||||||
|
|
||||||
const poly = new google.maps.Polygon({
|
|
||||||
strokeOpacity: 0.8,
|
|
||||||
strokeWeight: 2,
|
|
||||||
fillOpacity: 0.35,
|
|
||||||
editable: true,
|
|
||||||
draggable: true,
|
|
||||||
geodesic: true
|
|
||||||
});
|
|
||||||
|
|
||||||
poly.setMap(map);
|
|
||||||
|
|
||||||
this.$poly = poly;
|
|
||||||
|
|
||||||
if (this.startHash) {
|
|
||||||
this.loadHash(this.startHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
["insert_at", "remove_at", "set_at"].forEach(ev => google.maps.event.addListener(poly.getPath(), ev, this.surfaceUpdated));
|
|
||||||
this.updatePolygonColor();
|
|
||||||
|
|
||||||
this.$updateHashTimer = null;
|
|
||||||
this.mapLoaded = true;
|
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
const searchBox = new google.maps.places.SearchBox(pacinput.value);
|
||||||
density(val) {
|
currentMap.controls[google.maps.ControlPosition.LEFT_TOP].push(pacinput.value);
|
||||||
this.updatePolygonColor();
|
|
||||||
},
|
|
||||||
|
|
||||||
hash(hashval) {
|
searchBox.addListener('places_changed', () => {
|
||||||
if (this.$updateHashTimer) {
|
const places = searchBox.getPlaces();
|
||||||
clearTimeout(this.$updateHashTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$updateHashTimer = setTimeout(() => {
|
if (places.length == 0) {
|
||||||
this.$emit('hashChange', hashval);
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
getHue(val) {
|
|
||||||
const min = 0.1;
|
|
||||||
const max = 3.0;
|
|
||||||
|
|
||||||
// inv lerp from the density
|
|
||||||
const t = (val - min) / (max - min);
|
|
||||||
|
|
||||||
// lerp between green and red hue
|
|
||||||
const hue = (1.0 - t) * 110 + 0 * t;
|
|
||||||
|
|
||||||
return Math.max(0, Math.min(hue, 110));
|
|
||||||
},
|
|
||||||
|
|
||||||
updatePolygonColor() {
|
|
||||||
const hue = this.getHue(this.density);
|
|
||||||
|
|
||||||
this.$poly.setOptions({
|
|
||||||
fillColor: `hsl(${hue}, 90%, 50%)`,
|
|
||||||
strokeColor: `hsl(${hue}, 90%, 50%)`
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
mapUpdated() {
|
|
||||||
const pos = this.$map.getCenter();
|
|
||||||
const zoom = this.$map.getZoom();
|
|
||||||
|
|
||||||
this.mapPosition = [pos.lat().toFixed(7), pos.lng().toFixed(7), zoom];
|
|
||||||
},
|
|
||||||
|
|
||||||
mapClicked(ev) {
|
|
||||||
this.$poly.getPath().push(ev.latLng);
|
|
||||||
},
|
|
||||||
|
|
||||||
surfaceUpdated() {
|
|
||||||
this.surface = google.maps.geometry.spherical.computeArea(this.$poly.getPath()).toFixed(2);
|
|
||||||
this.arrPoly = this.$poly.getPath().getArray().slice();
|
|
||||||
|
|
||||||
this.$emit('surfaceUpdate', this.surface);
|
|
||||||
},
|
|
||||||
|
|
||||||
reloadHash(hash) {
|
|
||||||
this.loadHash(hash);
|
|
||||||
|
|
||||||
["insert_at", "remove_at", "set_at"].forEach(ev => google.maps.event.addListener(this.$poly.getPath(), ev, this.surfaceUpdated));
|
|
||||||
this.updatePolygonColor();
|
|
||||||
},
|
|
||||||
|
|
||||||
loadHash(hash) {
|
|
||||||
if (hash[0] != 'b') {
|
|
||||||
return this.loadLegacyHash(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
const buf = Base64.toUint8Array(hash.substr(1));
|
|
||||||
if (!buf) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = new Float32Array(buf.buffer, 0, 4);
|
const place = places[0];
|
||||||
const data = new Float32Array(buf.buffer, 4*4);
|
|
||||||
|
|
||||||
this.$map.setCenter({lat: meta[1], lng: meta[2]});
|
currentMap.setCenter(place.geometry.location);
|
||||||
this.$map.setZoom(parseInt(meta[3]));
|
currentMap.setZoom(17);
|
||||||
|
|
||||||
let path = [];
|
reset();
|
||||||
for (let i = 0; i < data.length; i += 2) {
|
});
|
||||||
path.push({
|
|
||||||
lat: data[i],
|
|
||||||
lng: data[i+1]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.length) {
|
currentMap.addListener('bounds_changed', function() {
|
||||||
this.$poly.setPath(path);
|
searchBox.setBounds(currentMap.getBounds());
|
||||||
this.surfaceUpdated();
|
});
|
||||||
}
|
currentMap.addListener('center_changed', mapUpdated);
|
||||||
|
currentMap.addListener('zoom_changed', mapUpdated);
|
||||||
|
currentMap.addListener('click', mapClicked);
|
||||||
|
|
||||||
this.$emit('densityChange', parseFloat(meta[0]));
|
currentMap.setOptions({
|
||||||
},
|
draggableCursor:'crosshair',
|
||||||
|
clickableIcons: false,
|
||||||
|
disableDoubleClickZoom: true,
|
||||||
|
streetViewControl: false
|
||||||
|
});
|
||||||
|
|
||||||
loadLegacyHash(hash) {
|
|
||||||
let opt = hash.split(';');
|
|
||||||
|
|
||||||
let curPosition = opt.pop();
|
const poly = new google.maps.Polygon({
|
||||||
|
strokeOpacity: 0.8,
|
||||||
|
strokeWeight: 2,
|
||||||
|
fillOpacity: 0.35,
|
||||||
|
editable: true,
|
||||||
|
draggable: true,
|
||||||
|
geodesic: true
|
||||||
|
});
|
||||||
|
|
||||||
if (curPosition) {
|
poly.setMap(currentMap);
|
||||||
let cursetting = curPosition.split(',');
|
|
||||||
this.$map.setCenter({lat: parseFloat(cursetting[0]), lng: parseFloat(cursetting[1])});
|
|
||||||
this.$map.setZoom(parseInt(cursetting[2]));
|
|
||||||
}
|
|
||||||
|
|
||||||
let density = parseFloat(opt.pop()) || 1;
|
currentPolygon = poly;
|
||||||
|
|
||||||
let path = [];
|
if (props.startHash) {
|
||||||
|
loadHash(props.startHash);
|
||||||
for (let i = 0; i < opt.length; i++) {
|
|
||||||
let coord = opt[i].split(',');
|
|
||||||
path.push({
|
|
||||||
lat: parseFloat(coord[0]),
|
|
||||||
lng: parseFloat(coord[1])
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.length) {
|
|
||||||
this.$poly.setPath(path);
|
|
||||||
this.surfaceUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$emit('densityChange', density);
|
|
||||||
},
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.$poly.getPath().clear();
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
["insert_at", "remove_at", "set_at"].forEach(ev => google.maps.event.addListener(poly.getPath(), ev, surfaceUpdated));
|
||||||
hash() {
|
updatePolygonColor();
|
||||||
let buf = new Float32Array(this.arrPoly.length*2+4);
|
|
||||||
buf[0] = this.density;
|
|
||||||
buf.set(this.mapPosition, 1);
|
|
||||||
|
|
||||||
for (let i = 0; i < this.arrPoly.length; i++) {
|
$updateHashTimer = null;
|
||||||
buf[4+i*2] = this.arrPoly[i].lat();
|
mapLoaded.value = true;
|
||||||
buf[4+i*2+1] = this.arrPoly[i].lng();
|
});
|
||||||
}
|
})
|
||||||
return 'b' + Base64.fromUint8Array(new Uint8Array(buf.buffer), true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
const getHue = (val: number) => {
|
||||||
return {
|
const min = 0.1;
|
||||||
mapPosition: [48.862895, 2.286978, 18],
|
const max = 3.0;
|
||||||
surface: 0,
|
|
||||||
arrPoly: [],
|
// inv lerp from the density
|
||||||
mapLoaded: false
|
const t = (val - min) / (max - min);
|
||||||
}
|
|
||||||
}
|
// lerp between green and red hue
|
||||||
|
const hue = (1.0 - t) * 110 + 0 * t;
|
||||||
|
|
||||||
|
return Math.max(0, Math.min(hue, 110));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updatePolygonColor = () => {
|
||||||
|
const hue = getHue(props.density);
|
||||||
|
|
||||||
|
currentPolygon.setOptions({
|
||||||
|
fillColor: `hsl(${hue}, 90%, 50%)`,
|
||||||
|
strokeColor: `hsl(${hue}, 90%, 50%)`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapUpdated = () => {
|
||||||
|
const pos = currentMap.getCenter();
|
||||||
|
const zoom = currentMap.getZoom();
|
||||||
|
|
||||||
|
mapPosition.value = [pos.lat(), pos.lng(), zoom];
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapClicked = (ev: any) => {
|
||||||
|
currentPolygon.getPath().push(ev.latLng);
|
||||||
|
}
|
||||||
|
|
||||||
|
const surfaceUpdated = () => {
|
||||||
|
arrPoly.value = currentPolygon.getPath().getArray().slice();
|
||||||
|
|
||||||
|
emits('surfaceUpdate', google.maps.geometry.spherical.computeArea(currentPolygon.getPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
const reloadHash = (hash: string) => {
|
||||||
|
loadHash(hash);
|
||||||
|
|
||||||
|
["insert_at", "remove_at", "set_at"].forEach(ev => google.maps.event.addListener(currentPolygon.getPath(), ev, surfaceUpdated));
|
||||||
|
|
||||||
|
updatePolygonColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadHash = (hash: string) => {
|
||||||
|
if (hash[0] != 'b') {
|
||||||
|
return loadLegacyHash(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buf = Base64.toUint8Array(hash.substr(1));
|
||||||
|
if (!buf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = new Float32Array(buf.buffer, 0, 4);
|
||||||
|
const data = new Float32Array(buf.buffer, 4*4);
|
||||||
|
|
||||||
|
currentMap.setCenter({lat: meta[1], lng: meta[2]});
|
||||||
|
currentMap.setZoom(meta[3]);
|
||||||
|
|
||||||
|
let path = [];
|
||||||
|
for (let i = 0; i < data.length; i += 2) {
|
||||||
|
path.push({
|
||||||
|
lat: data[i],
|
||||||
|
lng: data[i+1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.length) {
|
||||||
|
currentPolygon.setPath(path);
|
||||||
|
surfaceUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
emits('densityChange', meta[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadLegacyHash = (hash: string) => {
|
||||||
|
let opt = hash.split(';');
|
||||||
|
|
||||||
|
let curPosition = opt.pop();
|
||||||
|
|
||||||
|
if (curPosition) {
|
||||||
|
let cursetting = curPosition.split(',');
|
||||||
|
currentMap.setCenter({lat: parseFloat(cursetting[0]), lng: parseFloat(cursetting[1])});
|
||||||
|
currentMap.setZoom(parseInt(cursetting[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let density = parseFloat(opt.pop()) || 1;
|
||||||
|
|
||||||
|
let path = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < opt.length; i++) {
|
||||||
|
let coord = opt[i].split(',');
|
||||||
|
path.push({
|
||||||
|
lat: parseFloat(coord[0]),
|
||||||
|
lng: parseFloat(coord[1])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.length) {
|
||||||
|
currentPolygon.setPath(path);
|
||||||
|
surfaceUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
emits('densityChange', density);
|
||||||
|
}
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
currentPolygon.getPath().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
watch(() => props.density, () => updatePolygonColor());
|
||||||
|
|
||||||
|
const hash = computed(() => {
|
||||||
|
let buf = new Float32Array(arrPoly.value.length*2+4);
|
||||||
|
buf[0] = props.density;
|
||||||
|
buf.set(mapPosition.value, 1);
|
||||||
|
|
||||||
|
for (let i = 0; i < arrPoly.value.length; i++) {
|
||||||
|
buf[4+i*2] = arrPoly.value[i].lat();
|
||||||
|
buf[4+i*2+1] = arrPoly.value[i].lng();
|
||||||
|
}
|
||||||
|
return 'b' + Base64.fromUint8Array(new Uint8Array(buf.buffer), true);
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(hash, (hashval: string) => {
|
||||||
|
if ($updateHashTimer) {
|
||||||
|
clearTimeout($updateHashTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
$updateHashTimer = setTimeout(() => {
|
||||||
|
emits('hashChange', hashval);
|
||||||
|
}, 300);
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
reset,
|
||||||
|
reloadHash
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"strict": true,
|
||||||
|
"exactOptionalPropertyTypes": false,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"sourceMap": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["esnext", "dom"],
|
||||||
|
"types": ["vite/client", "unplugin-icons/types/vue"],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "components.d.ts"]
|
||||||
|
}
|
|
@ -56,6 +56,11 @@
|
||||||
"@nodelib/fs.scandir" "2.1.5"
|
"@nodelib/fs.scandir" "2.1.5"
|
||||||
fastq "^1.6.0"
|
fastq "^1.6.0"
|
||||||
|
|
||||||
|
"@types/google.maps@^3.48.3":
|
||||||
|
version "3.48.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/google.maps/-/google.maps-3.48.3.tgz#c554fbd7076c75fdd82dbae0ff3d541f67f5c599"
|
||||||
|
integrity sha512-JQuLWAEXDorNfarQy22WbIezbPKhLnsTBnDw25vyg61USZLTK8KqE1glNyASKn2v2mUOcEIWFMNHX5hNzjuw/g==
|
||||||
|
|
||||||
"@types/parse-json@^4.0.0":
|
"@types/parse-json@^4.0.0":
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||||
|
|
Ładowanie…
Reference in New Issue