Station: choose coordinates on map

This commit is contained in:
Timo Volkmann 2019-06-01 15:44:48 +02:00
parent be7c23a83a
commit 3dac7ebb2c
8 changed files with 441 additions and 92 deletions

View File

@ -19,7 +19,9 @@
"openlayers": "^4.6.5",
"quasar": "^1.0.0-rc.2",
"vue-qrcode-reader": "^1.4.2",
"vuelayers": "^0.11.4"
"vuelayers": "^0.11.4",
"lodash": "^4.17.11",
"ol": "^5.3.1"
},
"devDependencies": {
"@quasar/app": "^1.0.0-rc.4",

View File

@ -0,0 +1,408 @@
<template>
<div style="height: 400px">
<vl-map v-if="mapVisible" class="map" ref="map" :load-tiles-while-animating="true"
:load-tiles-while-interacting="true"
@click="clickCoordinate = $event.coordinate" @postcompose="onMapPostCompose"
data-projection="EPSG:4326" @mounted="onMapMounted">
<!-- map view aka ol.View -->
<vl-view ref="view" :center.sync="computedCoords" :zoom.sync="zoom" :rotation.sync="rotation"></vl-view>
<!-- interactions -->
<vl-interaction-select :features.sync="selectedFeatures" v-if="drawType == null">
<template slot-scope="select">
<!-- select styles -->
<vl-style-box>
<vl-style-stroke color="#423e9e" :width="7"></vl-style-stroke>
<vl-style-fill :color="[254, 178, 76, 0.7]"></vl-style-fill>
<vl-style-circle :radius="5">
<vl-style-stroke color="#423e9e" :width="7"></vl-style-stroke>
<vl-style-fill :color="[254, 178, 76, 0.7]"></vl-style-fill>
</vl-style-circle>
</vl-style-box>
<vl-style-box :z-index="1">
<vl-style-stroke color="#d43f45" :width="2"></vl-style-stroke>
<vl-style-circle :radius="5">
<vl-style-stroke color="#d43f45" :width="2"></vl-style-stroke>
</vl-style-circle>
</vl-style-box>
<!--// select styles -->
<!-- selected feature popup -->
<vl-overlay class="feature-popup" v-for="feature in select.features" :key="feature.id" :id="feature.id"
:position="pointOnSurface(feature.geometry)" :auto-pan="true"
:auto-pan-animation="{ duration: 300 }">
<template slot-scope="popup">
<section class="card">
<header class="card-header">
<p class="card-header-title">
Feature ID {{ feature.id }}
</p>
<a class="card-header-icon" title="Close"
@click="selectedFeatures = selectedFeatures.filter(f => f.id !== feature.id)">
<b-icon icon="close"></b-icon>
</a>
</header>
<div class="card-content">
<div class="content">
<p>
Overlay popup content for Feature with ID <strong>{{ feature.id }}</strong>
</p>
<p>
Popup: {{ JSON.stringify(popup) }}
</p>
<p>
Feature: {{ JSON.stringify({ id: feature.id, properties: feature.properties }) }}
</p>
</div>
</div>
</section>
</template>
</vl-overlay>
<!--// selected popup -->
</template>
</vl-interaction-select>
<!--// interactions -->
<!-- geolocation -->
<vl-geoloc @update:position="onUpdatePosition">
<template slot-scope="geoloc">
<!-- !!Snippet -->
<vl-feature v-if="geoloc.position" id="position-feature">
<vl-geom-point :coordinates="geoloc.position"></vl-geom-point>
<vl-style-box>
<vl-style-icon src="./statics/map-marker.svg" :scale="2" :anchor="[0.095,0.17]"
:size="[64, 64]"></vl-style-icon>
</vl-style-box>
</vl-feature>
<!-- !!Snippet -->
</template>
</vl-geoloc>
<!--// geolocation -->
<!-- <vl-feature id="point">-->
<!-- <vl-geom-point :coordinates=""></vl-geom-point>-->
<!-- </vl-feature>-->
<!-- overlay marker with animation -->
<!-- !!Snippet -->
<!-- <vl-feature id="marker" ref="marker" :properties="{ start: Date.now(), duration: 2500 }">-->
<vl-feature id="marker" ref="marker">
<!-- <template slot-scope="feature">-->
<template>
<vl-geom-point :coordinates="center"></vl-geom-point>
<vl-style-box>
<vl-style-icon src="./statics/map-marker.svg" :scale="2" :anchor="[0.095,0.17]"
:size="[128, 128]"></vl-style-icon>
</vl-style-box>
<!-- overlay binded to feature -->
<!-- <vl-overlay v-if="feature.geometry" :position="pointOnSurface(feature.geometry)" :offset="[10, 10]">-->
<!-- <p class="is-light box content">-->
<!-- Always opened overlay for Feature ID <strong>{{ feature.id }}</strong>-->
<!-- </p>-->
<!-- </vl-overlay>-->
</template>
</vl-feature>
<!-- !!Snippet -->
<!--// overlay marker -->
<!-- base layers -->
<vl-layer-tile v-for="layer in baseLayers" :key="layer.name" :id="layer.name" :visible="layer.visible">
<component :is="'vl-source-' + layer.name" v-bind="layer"></component>
</vl-layer-tile>
<!--// base layers -->
<!-- draw components -->
<!-- <vl-layer-vector id="draw-pane" v-if="mapPanel.tab === 'state'">-->
<!-- <vl-source-vector ident="draw-target" :features.sync="drawnFeatures">-->
<!-- </vl-source-vector>-->
<!-- </vl-layer-vector>-->
<!-- <vl-interaction-draw v-if="mapPanel.tab === 'state' && drawType" source="draw-target" :type="drawType"></vl-interaction-draw>-->
<!-- <vl-interaction-modify v-if="mapPanel.tab === 'state'" source="draw-target"></vl-interaction-modify>-->
<!-- <vl-interaction-snap v-if="mapPanel.tab === 'state'" source="draw-target" :priority="10"></vl-interaction-snap>-->
</vl-map>
</div>
</template>
<script>
import Vue from 'vue'
import VueLayers from 'vuelayers'
import 'vuelayers/lib/style.css' // needs css-loader
import {camelCase, kebabCase} from 'lodash'
import {addProj, createMultiPointGeom, createProj, createStyle, findPointOnSurface} from 'vuelayers/lib/ol-ext'
import ScaleLine from 'ol/control/ScaleLine'
import FullScreen from 'ol/control/FullScreen'
import OverviewMap from 'ol/control/OverviewMap'
import ZoomSlider from 'ol/control/ZoomSlider'
Vue.use(VueLayers);
// Custom projection for static Image layer
let x = 1024 * 10000
let y = 968 * 10000
let imageExtent = [-x / 2, -y / 2, x / 2, y / 2]
let customProj = createProj({
code: 'xkcd-image',
units: 'pixels',
extent: imageExtent,
})
// add to the list of known projections
// after that it can be used by code
addProj(customProj)
const easeInOut = t => 1 - Math.pow(1 - t, 3)
const methods = {
camelCase,
pointOnSurface: findPointOnSurface,
geometryTypeToCmpName(type) {
return 'vl-geom-' + kebabCase(type)
},
/**
* Packman layer Style function factory
* @return {ol.StyleFunction}
*/
pacmanStyleFunc() {
const pacman = [
createStyle({
strokeColor: '#de9147',
strokeWidth: 3,
fillColor: [222, 189, 36, 0.8],
}),
]
const path = [
createStyle({
strokeColor: 'blue',
strokeWidth: 1,
}),
createStyle({
imageRadius: 5,
imageFillColor: 'orange',
geom(feature) {
// geometry is an LineString, convert it to MultiPoint to style vertex
return createMultiPointGeom(feature.getGeometry().getCoordinates())
},
}),
]
const eye = [
createStyle({
imageRadius: 6,
imageFillColor: '#444444',
}),
]
return function __pacmanStyleFunc(feature) {
switch (feature.getId()) {
case 'pacman':
return pacman
case 'pacman-path':
return path
case 'pacman-eye':
return eye
}
}
},
/**
* Cluster layer style function factory
* @return {ol.StyleFunction}
*/
clusterStyleFunc() {
const cache = {}
return function __clusterStyleFunc(feature) {
const size = feature.get('features').length
let style = cache[size]
if (!style) {
style = createStyle({
imageRadius: 10,
strokeColor: '#fff',
fillColor: '#3399cc',
text: size.toString(),
textFillColor: '#fff',
})
cache[size] = style
}
return [style]
}
},
selectFilter(feature) {
return ['position-feature'].indexOf(feature.getId()) === -1
},
onUpdatePosition(coordinate) {
console.log("onUpdatePosition")
this.deviceCoordinate = coordinate;
this.$emit('updatecoords', this.center);
},
onMapPostCompose({vectorContext, frameState}) {
if (!this.$refs.marker || !this.$refs.marker.$feature) return
const feature = this.$refs.marker.$feature
if (!feature.getGeometry() || !feature.getStyle()) return
const flashGeom = feature.getGeometry().clone()
const duration = feature.get('duration')
const elapsed = frameState.time - feature.get('start')
const elapsedRatio = elapsed / duration
const radius = easeInOut(elapsedRatio) * 35 + 5
const opacity = easeInOut(1 - elapsedRatio)
const fillOpacity = easeInOut(0.5 - elapsedRatio)
vectorContext.setStyle(createStyle({
imageRadius: radius,
fillColor: [119, 170, 203, fillOpacity],
strokeColor: [119, 170, 203, opacity],
strokeWidth: 2 + opacity,
}))
vectorContext.drawGeometry(flashGeom)
vectorContext.setStyle(feature.getStyle()(feature)[0])
vectorContext.drawGeometry(feature.getGeometry())
if (elapsed > duration) {
feature.set('start', Date.now())
}
this.$refs.map.render()
},
onMapMounted() {
// now ol.Map instance is ready and we can work with it directly
this.$refs.map.$map.getControls().extend([
new ScaleLine(),
new FullScreen(),
new OverviewMap({
collapsed: false,
collapsible: true,
}),
new ZoomSlider(),
])
},
// base layers
showBaseLayer(name) {
let layer = this.baseLayers.find(layer => layer.visible)
if (layer != null) {
layer.visible = false
}
layer = this.baseLayers.find(layer => layer.name === name)
if (layer != null) {
layer.visible = true
}
},
// map panel
mapPanelTabClasses(tab) {
return {
'is-active': this.mapPanel.tab === tab,
}
},
showMapPanelLayer(layer) {
layer.visible = !layer.visible
},
showMapPanelTab(tab) {
this.mapPanel.tab = tab
if (tab !== 'draw') {
this.drawType = undefined
}
},
}
export default {
name: 'mapclickable',
methods,
props: {
coords: Array,
cacheObject: Object,
stationObject: Object,
},
data() {
return {
center: [9.208858198755664, 49.14785422283188],
zoom: 15,
rotation: 0,
clickCoordinate: undefined,
selectedFeatures: [],
deviceCoordinate: undefined,
mapPanel: {
tab: 'state',
},
panelOpen: true,
mapVisible: true,
drawControls: [
{
type: 'point',
label: 'Draw Point',
icon: 'map-marker',
},
{
type: 'line-string',
label: 'Draw LineString',
icon: 'minus',
},
{
type: 'polygon',
label: 'Draw Polygon',
icon: 'square-o',
},
{
type: 'circle',
label: 'Draw Circle',
icon: 'circle-thin',
},
{
type: undefined,
label: 'Stop drawing',
icon: 'times',
},
],
drawType: "point",
drawnFeatures: [],
// base layers
baseLayers: [
{
name: 'osm',
title: 'OpenStreetMap',
visible: true,
},
{
name: 'sputnik',
title: 'Sputnik Maps',
visible: false,
},
// needs paid plan to get key
// {
// name: 'mapbox',
// title: 'Mapbox',
// },
{
name: 'bingmaps',
title: 'Bing Maps',
apiKey: 'ArbsA9NX-AZmebC6VyXAnDqjXk6mo2wGCmeYM8EwyDaxKfQhUYyk0jtx6hX5fpMn',
imagerySet: 'CanvasGray',
visible: false,
},
],
// layers config
}
},
mounted() {
},
computed: {
computedCoords: {
get() {
return this.center;
},
set(val) {
this.center = val;
this.$emit('updatecoords', this.center);
}
}
}
}
</script>
<style>
</style>

View File

@ -251,8 +251,8 @@
}
},
validateRankingPoints: function () {
var re = new RegExp('^[0-9]+$');
var rps = String.valueOf(this.computedCache.rankingPoints);
let re = new RegExp('^[0-9]+$');
let rps = String(this.computedCache.rankingPoints);
return re.test(rps);
},
validateEverything(){

View File

@ -15,25 +15,32 @@
<!--type="textarea"-->
<!--/>-->
<p class="text-h6 q-mt-md">Location</p>
<vl-map :load-tiles-while-animating="true" :load-tiles-while-interacting="true"
data-projection="EPSG:4326">
<vl-view :zoom.sync="zoom" :center.sync="center" :rotation.sync="rotation"></vl-view>
<mapclick :coords="[this.station.longitude, this.station.lattitude]" @updatecoords="updateCoords($event)">
<vl-geoloc @update:position="geolocPosition = $event">
<template slot-scope="geoloc">
<vl-feature v-if="geoloc.position" id="position-feature">
<vl-geom-point :coordinates="geoloc.position"></vl-geom-point>
</vl-feature>
</template>
</vl-geoloc>
</mapclick>
<!-- <vl-map :load-tiles-while-animating="true" :load-tiles-while-interacting="true"-->
<!-- data-projection="EPSG:4326">-->
<!-- <vl-view :zoom.sync="zoom" :center.sync="center" :rotation.sync="rotation"></vl-view>-->
<vl-layer-tile id="osm">
<vl-source-osm></vl-source-osm>
</vl-layer-tile>
</vl-map>
<!-- <vl-geoloc @update:position="geolocPosition = $event">-->
<!-- <template slot-scope="geoloc">-->
<!-- <vl-feature v-if="geoloc.position" id="position-feature">-->
<!-- <vl-geom-point :coordinates="geoloc.position"></vl-geom-point>-->
<!-- </vl-feature>-->
<!-- </template>-->
<!-- </vl-geoloc>-->
<!-- <vl-layer-tile id="osm">-->
<!-- <vl-source-osm></vl-source-osm>-->
<!-- </vl-layer-tile>-->
<!-- </vl-map>-->
<div class="row q-col-gutter-md">
<q-input class="col" dense stack-label filled v-model="latlang" @input="separateLatlang"
label="Breitengrad/Längengrad"/>
<q-input class="col-12" dense stack-label filled v-model="station.lattitude"
label="Breitengrad"/>
<q-input class="col-12" dense stack-label filled v-model="station.longitude"
label="Längengrad"/>
<div class="col-shrink">
<q-btn unelevated color="primary" class="full-height" icon="my_location"/>
</div>
@ -51,10 +58,11 @@
<script>
import {mapGetters} from 'vuex';
import mapclick from "../components/mapClickable";
export default {
name: "Station",
components: { mapclick },
data() {
return {
description: "Rätsel, Aufgabe und Informationen zur Station.",
@ -91,8 +99,12 @@
...mapGetters({
tempStation: 'cacheCollector/GET_TEMPSTATION'
}),
},
},
methods: {
updateCoords(event) {
this.station.longitude = event[0];
this.station.lattitude = event[1];
},
separateLatlang() {
//console.log("separateLatlang()");
if (this.latlang.includes(',')) {

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1"
id="Ebene_1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 24"
style="enable-background:new 0 0 24 24;" xml:space="preserve">
<style type="text/css">
.st0{fill:#1E64F8;}
.st1{fill:#1C5FC5;}
</style>
<g transform="translate(0 -1028.4)">
<path class="st0" d="M12,1028.4c-4.4,0-8,3.6-8,8c0,1.4,0.4,2.8,1,3.9c0.1,0.2,0.2,0.4,0.3,0.6l6.6,11.5l6.6-11.5
c0.1-0.2,0.2-0.3,0.3-0.5l0.1-0.1c0.6-1.2,1-2.5,1-3.9C20,1032,16.4,1028.4,12,1028.4z M12,1032.4c2.2,0,4,1.8,4,4c0,2.2-1.8,4-4,4
c-2.2,0-4-1.8-4-4C8,1034.2,9.8,1032.4,12,1032.4z"/>
<path class="st1" d="M12,1031.4c-2.8,0-5,2.2-5,5c0,2.8,2.2,5,5,5c2.8,0,5-2.2,5-5C17,1033.6,14.8,1031.4,12,1031.4z M12,1033.4
c1.7,0,3,1.3,3,3s-1.3,3-3,3s-3-1.3-3-3S10.3,1033.4,12,1033.4z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1"
id="Ebene_1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 24"
style="enable-background:new 0 0 24 24;" xml:space="preserve">
<style type="text/css">
.st0{fill:#509B1A;}
.st1{fill:#368220;}
</style>
<g transform="translate(0 -1028.4)">
<path class="st0" d="M12,1028.4c-4.4,0-8,3.6-8,8c0,1.4,0.4,2.8,1,3.9c0.1,0.2,0.2,0.4,0.3,0.6l6.6,11.5l6.6-11.5
c0.1-0.2,0.2-0.3,0.3-0.5l0.1-0.1c0.6-1.2,1-2.5,1-3.9C20,1032,16.4,1028.4,12,1028.4z M12,1032.4c2.2,0,4,1.8,4,4c0,2.2-1.8,4-4,4
c-2.2,0-4-1.8-4-4C8,1034.2,9.8,1032.4,12,1032.4z"/>
<path class="st1" d="M12,1031.4c-2.8,0-5,2.2-5,5c0,2.8,2.2,5,5,5c2.8,0,5-2.2,5-5C17,1033.6,14.8,1031.4,12,1031.4z M12,1033.4
c1.7,0,3,1.3,3,3s-1.3,3-3,3s-3-1.3-3-3S10.3,1033.4,12,1033.4z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1"
id="Ebene_1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 24"
style="enable-background:new 0 0 24 24;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.75;}
.st1{fill:#636363;}
.st2{fill:#404040;}
</style>
<g transform="translate(0 -1028.4)" class="st0">
<path class="st1" d="M12,1028.4c-4.4,0-8,3.6-8,8c0,1.4,0.4,2.8,1,3.9c0.1,0.2,0.2,0.4,0.3,0.6l6.6,11.5l6.6-11.5
c0.1-0.2,0.2-0.3,0.3-0.5l0.1-0.1c0.6-1.2,1-2.5,1-3.9C20,1032,16.4,1028.4,12,1028.4z M12,1032.4c2.2,0,4,1.8,4,4c0,2.2-1.8,4-4,4
c-2.2,0-4-1.8-4-4C8,1034.2,9.8,1032.4,12,1032.4z"/>
<path class="st2" d="M12,1031.4c-2.8,0-5,2.2-5,5c0,2.8,2.2,5,5,5c2.8,0,5-2.2,5-5C17,1033.6,14.8,1031.4,12,1031.4z M12,1033.4
c1.7,0,3,1.3,3,3s-1.3,3-3,3s-3-1.3-3-3S10.3,1033.4,12,1033.4z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1"
id="Ebene_1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 24"
style="enable-background:new 0 0 24 24;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFD137;}
.st1{fill:#ECBD4B;}
</style>
<g transform="translate(0 -1028.4)">
<path class="st0" d="M12,1028.4c-4.4,0-8,3.6-8,8c0,1.4,0.4,2.8,1,3.9c0.1,0.2,0.2,0.4,0.3,0.6l6.6,11.5l6.6-11.5
c0.1-0.2,0.2-0.3,0.3-0.5l0.1-0.1c0.6-1.2,1-2.5,1-3.9C20,1032,16.4,1028.4,12,1028.4z M12,1032.4c2.2,0,4,1.8,4,4c0,2.2-1.8,4-4,4
c-2.2,0-4-1.8-4-4C8,1034.2,9.8,1032.4,12,1032.4z"/>
<path class="st1" d="M12,1031.4c-2.8,0-5,2.2-5,5c0,2.8,2.2,5,5,5c2.8,0,5-2.2,5-5C17,1033.6,14.8,1031.4,12,1031.4z M12,1033.4
c1.7,0,3,1.3,3,3s-1.3,3-3,3s-3-1.3-3-3S10.3,1033.4,12,1033.4z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB