Famo.us drag & drop surfaces? - drag-and-drop

I've gone through the handy Famo.us University tutorials and am prototyping a drag & drop interface. It's the typical UI where the user can drag an icon and drop it onto a target to do something. I've gotten the drag part down, but detecting the drop is getting very hairy. Is there built-in collision detection in Famo.us?
Edit: I've looked at the Collision API but it's not clear whether this would work across views.
Here's how I've organized the project:
AppView (overall container)
|
|__ MenuView (sidebar) --> VizView (icons in MenuView)
|
|__ PageView (workspace where the drop targets live)
This may not be the best way to go about this. I'm not sure. Hooking up input events across the views seems to be painful.
VizView source:
/*** VizView.js ***/
define(function(require, exports, module) {
var View = require('famous/core/View');
var Surface = require('famous/core/Surface');
var Transform = require('famous/core/Transform');
var Modifier = require('famous/core/Modifier');
var ImageSurface = require('famous/surfaces/ImageSurface');
var Transitionable = require("famous/transitions/Transitionable");
var SnapTransition = require("famous/transitions/SnapTransition");
Transitionable.registerMethod("spring", SnapTransition);
var GenericSync = require('famous/inputs/GenericSync');
var MouseSync = require('famous/inputs/MouseSync');
var TouchSync = require('famous/inputs/TouchSync');
GenericSync.register({'mouse': MouseSync, 'touch': TouchSync});
function VizView() {
View.apply(this, arguments);
_createIcon.call(this);
}
VizView.prototype = Object.create(View.prototype);
VizView.prototype.constructor = VizView;
VizView.DEFAULT_OPTIONS = {
width: 200,
height: 100,
angle: -0.2,
iconSize: 98,
iconUrl: '',
title: 'Empty',
fontSize: 26
};
function _createIcon() {
this.zIndex = 0;
var me = this;
var iconSurface = new ImageSurface({
size: [this.options.iconSize, this.options.iconSize],
content : this.options.iconUrl,
properties: {
cursor: 'pointer'
}
});
var initModifier = new Modifier({
// places the icon in the proper location
transform: Transform.translate(24, 2, 0)
});
this.position = new Transitionable([0, 0]);
var positionModifier = new Modifier({
transform : function(){
var currentPosition = me.position.get();
return Transform.translate(currentPosition[0], currentPosition[1], me.zIndex);
},
});
var sync = new GenericSync(
['mouse', 'touch']
);
sync.on('start', function(data){
me.zIndex = 1;
});
sync.on('update', function(data){
me.updateIcon(data);
});
sync.on('end', function(data){
var velocity = data.velocity;
me.position.set([0, 0], {
method : 'spring',
period : 150,
velocity : velocity
});
me.zIndex = 0;
});
iconSurface.pipe(sync);
this.add(positionModifier).add(initModifier).add(iconSurface);
this.updateIcon = function (data) {
if (this.zIndex == 0) return;
var currentPosition = this.position.get();
this.position.set([
currentPosition[0] + data.delta[0],
currentPosition[1] + data.delta[1]
]);
}
}
module.exports = VizView;
});
A VizView is instantiated in MenuView as such:
var vizView = new VizView({
iconUrl: "path/to/iconUrl",
title: "Viz Title"
});
var vizModifier = new StateModifier({
transform: Transform.translate(0, yOffset, 0)
});
this.add(vizModifier).add(vizView);

A draggable Surface in Famo.us is not really a DOM draggable element although it can be setup to work in a browser using the mouse. I have not been able to get GenericSync and touch to work with this solution yet.
Reading the pitfalls on the Famo.us site, there are hints to drag and drop with surface draggables being an issue.
How do I find the absolute position of a Surface on the screen?
By design this is not possible. It is something the developer should
not care about. For the time being, this means that interactions such
as drag and drop are harder to implement, but this is intended and we
are working on an elegant solution for these use-cases.
Although: When not using GenericSync, you can use the DOM draggable events with a Famo.us Surface as you stated in the comments and link to the John Traver solution.
But: This solution will not work on mobile touch devices using Famo.us at the time of this answer. Getting this to work with touch may prove to be more difficult as stated in the pitfalls. Let's hope this gets solved in versions following 0.3.5 or in MixedMode (WebGL and DOM)
define('main', function(require, exports, module) {
var Engine = require('famous/core/Engine');
var Surface = require('famous/core/Surface');
var ImageSurface = require('famous/surfaces/ImageSurface');
var Transform = require('famous/core/Transform');
var Modifier = require('famous/core/Modifier');
var StateModifier = require('famous/modifiers/StateModifier');
var Draggable = require('famous/modifiers/Draggable');
var TransitionableTransform = require('famous/transitions/TransitionableTransform');
var mainContext = Engine.createContext();
var transTransform = new TransitionableTransform();
transTransform.set(Transform.translate(100, 0, 0));
var captureSurface = new Surface({
content: 'Drag to Here',
size: [300, 300],
properties: {
textAlign: 'center',
lineHeight: '300px',
backgroundColor: 'rgba(255,255,0,0.4)',
cursor: 'pointer'
},
attributes: {
dropzone: 'copy file:image/png file:image/gif file:image/jpeg'
}
});
captureSurface.on('dragenter', function(evt) {
console.log('dragenter', evt);
evt.preventDefault();
return false;
});
captureSurface.on('dragleave', function(evt) {
console.log('dragleave', evt);
captureSurface.setProperties({
border: 'none'
});
evt.preventDefault();
return false;
});
captureSurface.on('dragover', function(evt) {
console.log('dragover', evt);
captureSurface.setProperties({
border: '4px dashed black'
});
evt.preventDefault();
return false;
});
captureSurface.on('drop', function(evt) {
console.log('drop', evt);
evt.preventDefault();
evt.stopPropagation();
captureSurface.setProperties({
border: '4px solid red'
});
files = evt.dataTransfer.files;
console.log(files);
});
mainContext.add(new Modifier({
origin: [0.5, 0.5],
align: [0.5, 0.5]
})).add(captureSurface);
var surface = new Surface({
content: 'DOM Draggable',
size: [300, 100],
properties: {
backgroundColor: 'rgba(255,0,0,0.4)',
cursor: 'move'
},
attributes: {
draggable: 'true'
}
});
surface.on('drag', function(evt) {
console.log('surface drag', evt)
});
var imageSurface = new ImageSurface({
content: 'http://i.imgur.com/NGOwZeT.png',
size: [100, 100],
properties: {
cursor: 'copy'
},
attributes: {
draggable: 'true'
}
});
imageSurface.on('drag', function(evt) {
console.log('imageSurface drag', evt)
});
imageSurface.on('dragend', function(evt) {
console.log('imageSurface dragend', evt)
});
var dragSurface = new Surface({
content: 'Drag Me',
size: [100, 100],
properties: {
backgroundColor: 'rgba(0,0,0,0.1)',
cursor: 'move'
},
attributes: {
draggable: 'true'
}
});
dragSurface.on('dragstart', function(evt) {
console.log('dragSurface dragstart', event, evt);
});
dragSurface.on('drag', function(evt) {
console.log('dragSurface dragstart', event, evt);
});
var modifier = new Modifier({
origin: [0, 0],
align: [0, 0],
transform: transTransform
});
var imageModifier = new Modifier({
origin: [0, 0.5],
align: [0, 0.5]
});
var draggable = new Draggable();
draggable.subscribe(dragSurface);
mainContext.add(modifier).add(surface);
mainContext.add(imageModifier).add(imageSurface);
mainContext.add(draggable).add(dragSurface);
draggable.on('update', function(e) {
console.log('draggable update', e, event);
var pos = e.position;
surface.setContent('Draggable Position is ' + pos);
transTransform.set(Transform.translate(pos[0] + 100, pos[1], 0));
});
draggable.on('end', function(e) {
var pos = e.position;
surface.setContent('Draggable End Position is ' + pos);
transTransform.set(Transform.translate(pos[0] + 100, pos[1], 0));
});
//draggable.deactivate();
});
require(['main']);
<script src="http://requirejs.org/docs/release/2.1.16/minified/require.js"></script>
<script src="http://code.famo.us/lib/requestAnimationFrame.js"></script>
<script src="http://code.famo.us/lib/classList.js"></script>
<script src="http://code.famo.us/lib/functionPrototypeBind.js"></script>
<link rel="stylesheet" type="text/css" href="http://code.famo.us/famous/0.3.5/famous.css" />
<script src="http://code.famo.us/famous/0.3.5/famous.min.js"></script>

Related

Leaflet: How can I add a marker to map only on mouseover another marker?

I have a polygon and two markers. The last marker (var power) I want to start only on mouseover or onclick the first marker (var myIcon). How can I do that? Could you please take a look on this code?
var polygon = new L.Polygon(line, {
color: pastel,
weight: 0.1,
opacity: 0.1,
fillColor: pastel,
fillOpacity: 0.04,
interactive: true
});
polygon.addTo(map)
}
var myIcon = L.divIcon({
className: 'divIcon',
iconSize: new L.Point(35, 15),
iconAnchor:[18, 20],
zIndexOffset: 1000,
html: '<?=$desc1[$i]?>'
});
var marker = L.marker([x1, y1], {icon: myIcon})
.addTo(map)
marker.refPoly = polygon;
marker.on('mouseover', function(e) {
e.target.refPoly.setStyle({
fillOpacity: 0.48
});
});
marker.on('mouseout', function(e) {
e.target.refPoly.setStyle({
fillOpacity: 0.04
});
});
var power = <?php echo json_encode($watt); ?>;
var power = power.reverse();
var myPower = L.divIcon({
className: 'divPower',
iconSize: new L.Point(25, 12),
iconAnchor:[12, 5],
html: power[b]
});
L.marker(pointC, {icon: myPower}).addTo(map)
.bindTooltip(350-b*10 + '°');
You can remove a marker with marker.removeFrom(map) and add it to the map with marker.addTo(map):
var marker = L.marker([x1, y1], {icon: myIcon})
.addTo(map)
marker.refPoly = polygon;
var power = <?php echo json_encode($watt); ?>;
var power = power.reverse();
var myPower = L.divIcon({
className: 'divPower',
iconSize: new L.Point(25, 12),
iconAnchor:[12, 5],
html: power[b]
});
// I removed .addTo(map). Then the marker is not displayed from beginning
var powerMarker = L.marker(pointC, {icon: myPower})
.bindTooltip(350-b*10 + '°');
marker.on('mouseover', function(e) {
e.target.refPoly.setStyle({
fillOpacity: 0.48
});
powerMarker.addTo(map);
});
marker.on('mouseout', function(e) {
e.target.refPoly.setStyle({
fillOpacity: 0.04
});
powerMarker.removeFrom(map);
});
marker.on('click', function(e) {
powerMarker.addTo(map);
});

How to add marker onto polygon layer using leaflet.pm?

Lines and polygons are added without problems, but when trying to add markers on top of a polygon layer ("lyrPoly"), it interprets that I want to click on the polygon (e.g. opens popup) instead of adding marker. How can I avoid this? I am using leaflet.pm to add markers, linestrings and polygons.
This is the code for the marker:
map.on('pm:create', function(e) {
e.marker;
var type = e.layerType,
layer = e.layer;
$(document).ready(function() {
var jsnMarker = layer.toGeoJSON().geometry;
jsnMarker = {
type: "MultiPoint",
coordinates: [jsnMarker.coordinates]
};
})
});
$(function() {
$('.check').change(function() {
var data = $(this).val();
if (data == "versionOne") {
var markerStyle = {
draggable: false,
icon: customIcon,
};
var options = {
markerStyle,
}
map.pm.enableDraw('Marker', options);
} else if (data == "versionTwo") {
var markerStyle = {
draggable: false,
icon: otherIcon,
};
var options = {
markerStyle,
}
map.pm.enableDraw('Marker', options);
}
$('.check').trigger('change');
});
And inside the ajax function I use this:
lyrMarker = L.geoJSON(jsnMarker, {
pointToLayer: function (feature, latlng) {
if(feature.properties.marker_type=="versionOne"){
return L.marker(latlng, {
icon: customIcon
})
}else if
(feature.properties.marker_type=="versionTwo"){
return L.marker(latlng, {
icon: otherIcon
})
} ,
onEachFeature: forEachMarker
});
For the polygon-layer I have a corresponding "onEachFeature"-function that looks like this:
function forEachFeature(feature, layer) {
var att = feature.properties;
var popupContent =
"Popup-content" ;
layer.bindPopup(popupContent, {
offset: [0, -120]
});
layer.on("click", function (e) {
lyrPoly.setStyle(style); //resets layer colors
layer.setStyle(highlight); //highlights selected.
});
};
Adding marker to the background/basemap works fine, but not onto the polygon layer. Why is that, and what would be a good solution? I don't want to add the marker to the same layer as the polygons, but avoid polygons "getting in the way".
I've had ideas about making the polygon layer interactive: false while in adding-marker-mode, but haven't succeeded.
Update your click event and test if drawing mode is enabled:
function forEachFeature(feature, layer) {
var att = feature.properties;
var popupContent =
"Popup-content" ;
layer.bindPopup(popupContent, {
offset: [0, -120]
});
layer.on("click", function (e) {
if(!map.pm.Draw.Marker.enabled()){
lyrPoly.setStyle(style); //resets layer colors
layer.setStyle(highlight); //highlights selected.
}
});
};
Update
To disable the popup open event add following code at the top of your file. Before you create a Layer with a Popup:
L.Layer.include({
_openPopup: function (e) {
var layer = e.layer || e.target;
//This IF is new
if(map.pm.Draw.Marker.enabled()){
return;
}
if (!this._popup) {
return;
}
if (!this._map) {
return;
}
// prevent map click
L.DomEvent.stop(e);
// if this inherits from Path its a vector and we can just
// open the popup at the new location
if (layer instanceof L.Path) {
this.openPopup(e.layer || e.target, e.latlng);
return;
}
// otherwise treat it like a marker and figure out
// if we should toggle it open/closed
if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
this.closePopup();
} else {
this.openPopup(layer, e.latlng);
}
},
});
Example: https://jsfiddle.net/falkedesign/dg4rpcqe/

OpenLayers 5: move marker smoothly

I use
https://openlayers.org/en/latest/examples/feature-move-animation.html
code snippet:
// This long string is placed here due to jsFiddle limitations.
// It is usually loaded with AJAX.
var polyline = [
'hldhx#lnau`BCG_EaC??cFjAwDjF??uBlKMd#}#z#??aC^yk#z_#se#b[wFdE??wFfE}N',
'fIoGxB_I\\gG}#eHoCyTmPqGaBaHOoD\\??yVrGotA|N??o[N_STiwAtEmHGeHcAkiA}^',
'aMyBiHOkFNoI`CcVvM??gG^gF_#iJwC??eCcA]OoL}DwFyCaCgCcCwDcGwHsSoX??wI_E',
'kUFmq#hBiOqBgTwS??iYse#gYq\\cp#ce#{vA}s#csJqaE}{#iRaqE{lBeRoIwd#_T{]_',
'Ngn#{PmhEwaA{SeF_u#kQuyAw]wQeEgtAsZ}LiCarAkVwI}D??_}RcjEinPspDwSqCgs#',
'sPua#_OkXaMeT_Nwk#ob#gV}TiYs[uTwXoNmT{Uyb#wNg]{Nqa#oDgNeJu_#_G}YsFw]k',
'DuZyDmm#i_#uyIJe~#jCg|#nGiv#zUi_BfNqaAvIow#dEed#dCcf#r#qz#Egs#{Acu#mC',
'um#yIey#gGig#cK_m#aSku#qRil#we#{mAeTej#}Tkz#cLgr#aHko#qOmcEaJw~C{w#ka',
'i#qBchBq#kmBS{kDnBscBnFu_Dbc#_~QHeU`IuyDrC_}#bByp#fCyoA?qMbD}{AIkeAgB',
'k_A_A{UsDke#gFej#qH{o#qGgb#qH{`#mMgm#uQus#kL{_#yOmd#ymBgwE}x#ouBwtA__',
'DuhEgaKuWct#gp#cnBii#mlBa_#}|Asj#qrCg^eaC}L{dAaJ_aAiOyjByH{nAuYu`GsAw',
'Xyn#ywMyOyqD{_#cfIcDe}#y#aeBJmwA`CkiAbFkhBlTgdDdPyiB`W}xDnSa}DbJyhCrX',
'itAhT}x#bE}Z_#qW_Kwv#qKaaAiBgXvIm}A~JovAxCqW~WanB`XewBbK{_A`K}fBvAmi#',
'xBycBeCauBoF}}#qJioAww#gjHaPopA_NurAyJku#uGmi#cDs[eRaiBkQstAsQkcByNma',
'CsK_uBcJgbEw#gkB_#ypEqDoqSm#eZcDwjBoGw`BoMegBaU_`Ce_#_uBqb#ytBwkFqiT_',
'fAqfEwe#mfCka#_eC_UmlB}MmaBeWkkDeHwqAoX}~DcBsZmLcxBqOwqE_DkyAuJmrJ\\o',
'~CfIewG|YibQxBssB?es#qGciA}RorAoVajA_nAodD{[y`AgPqp#mKwr#ms#umEaW{dAm',
'b#umAw|#ojBwzDaaJsmBwbEgdCsrFqhAihDquAi`Fux#}_Dui#_eB_u#guCuyAuiHukA_',
'lKszAu|OmaA{wKm}#clHs_A_rEahCssKo\\sgBsSglAqk#yvDcS_wAyTwpBmPc|BwZknF',
'oFscB_GsaDiZmyMyLgtHgQonHqT{hKaPg}Dqq#m~Hym#c`EuiBudIabB{hF{pWifx#snA',
'w`GkFyVqf#y~BkoAi}Lel#wtc#}`#oaXi_C}pZsi#eqGsSuqJ|Lqeb#e]kgPcaAu}SkDw',
'zGhn#gjYh\\qlNZovJieBqja#ed#siO{[ol\\kCmjMe\\isHorCmec#uLebB}EqiBaCg}',
'#m#qwHrT_vFps#kkI`uAszIrpHuzYxx#e{Crw#kpDhN{wBtQarDy#knFgP_yCu\\wyCwy',
'A{kHo~#omEoYmoDaEcPiuAosDagD}rO{{AsyEihCayFilLaiUqm#_bAumFo}DgqA_uByi',
'#swC~AkzDlhA}xEvcBa}Cxk#ql#`rAo|#~bBq{#``Bye#djDww#z_C_cAtn#ye#nfC_eC',
'|gGahH~s#w}#``Fi~FpnAooC|u#wlEaEedRlYkrPvKerBfYs}Arg#m}AtrCkzElw#gjBb',
'h#woBhR{gCwGkgCc[wtCuOapAcFoh#uBy[yBgr#c#iq#o#wvEv#sp#`FajBfCaq#fIipA',
'dy#ewJlUc`ExGuaBdEmbBpBssArAuqBBg}#s#g{AkB{bBif#_bYmC}r#kDgm#sPq_BuJ_',
's#{X_{AsK_d#eM{d#wVgx#oWcu#??aDmOkNia#wFoSmDyMyCkPiBePwAob#XcQ|#oNdCo',
'SfFwXhEmOnLi\\lbAulB`X_d#|k#au#bc#oc#bqC}{BhwDgcD`l#ed#??bL{G|a#eTje#',
'oS~]cLr~Bgh#|b#}Jv}EieAlv#sPluD{z#nzA_]`|KchCtd#sPvb#wSb{#ko#f`RooQ~e',
'[upZbuIolI|gFafFzu#iq#nMmJ|OeJn^{Qjh#yQhc#uJ~j#iGdd#kAp~BkBxO{#|QsAfY',
'gEtYiGd]}Jpd#wRhVoNzNeK`j#ce#vgK}cJnSoSzQkVvUm^rSgc#`Uql#xIq\\vIgg#~k',
'Dyq[nIir#jNoq#xNwc#fYik#tk#su#neB}uBhqEesFjoGeyHtCoD|D}Ed|#ctAbIuOzqB',
'_}D~NgY`\\um#v[gm#v{Cw`G`w#o{AdjAwzBh{C}`Gpp#ypAxn#}mAfz#{bBbNia#??jI',
'ab#`CuOlC}YnAcV`#_^m#aeB}#yk#YuTuBg^uCkZiGk\\yGeY}Lu_#oOsZiTe[uWi[sl#',
'mo#soAauAsrBgzBqgAglAyd#ig#asAcyAklA}qAwHkGi{#s~#goAmsAyDeEirB_{B}IsJ',
'uEeFymAssAkdAmhAyTcVkFeEoKiH}l#kp#wg#sj#ku#ey#uh#kj#}EsFmG}Jk^_r#_f#m',
'~#ym#yjA??a#cFd#kBrCgDbAUnAcBhAyAdk#et#??kF}D??OL'
].join('');
var route = /** #type {module:ol/geom/LineString~LineString} */ (new ol.format.Polyline({
factor: 1e6
}).readGeometry(polyline, {
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857'
}));
var routeCoords = route.getCoordinates();
var routeLength = routeCoords.length;
var routeFeature = new ol.Feature({
type: 'route',
geometry: route
});
var geoMarker = new ol.Feature({
type: 'geoMarker',
geometry: new ol.geom.Point(routeCoords[0])
});
var startMarker = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords[0])
});
var endMarker = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords[routeLength - 1])
});
var styles = {
'route': new ol.style.Style({
stroke: new ol.style.Stroke({
width: 6,
color: [237, 212, 0, 0.8]
})
}),
'icon': new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1],
src: 'https://openlayers.org/en/v4.6.5/examples/data/icon.png'
})
}),
'geoMarker': new ol.style.Style({
image: new ol.style.Circle({ // CircleStyle({
radius: 7,
snapToPixel: false,
fill: new ol.style.Fill({
color: 'black'
}),
stroke: new ol.style.Stroke({
color: 'white',
width: 2
})
})
})
};
var animating = false;
var speed, now;
var speedInput = document.getElementById('speed');
var startButton = document.getElementById('start-animation');
var vectorLayer = new ol.layer.Vector({ // VectorLayer({
source: new ol.source.Vector({ // VectorSource({
features: [routeFeature, geoMarker, startMarker, endMarker]
}),
style: function(feature) {
// hide geoMarker if animation is active
if (animating && feature.get('type') === 'geoMarker') {
return null;
}
return styles[feature.get('type')];
}
});
var center = [-5639523.95, -3501274.52];
var map = new ol.Map({
target: document.getElementById('map'),
loadTilesWhileAnimating: true,
view: new ol.View({
center: center,
zoom: 9,
minZoom: 2,
maxZoom: 19
}),
layers: [
new ol.layer.Tile({ // TileLayer({
source: new ol.source.OSM()
}),
vectorLayer
]
});
var moveFeature = function(event) {
var vectorContext = event.vectorContext;
var frameState = event.frameState;
if (animating) {
var elapsedTime = frameState.time - now;
// here the trick to increase speed is to jump some indexes
// on lineString coordinates
var index = Math.round(speed * elapsedTime / 1000);
if (index >= routeLength) {
stopAnimation(true);
return;
}
var currentPoint = new ol.geom.Point(routeCoords[index]);
var feature = new ol.Feature(currentPoint);
vectorContext.drawFeature(feature, styles.geoMarker);
}
// tell OpenLayers to continue the postcompose animation
map.render();
};
function startAnimation() {
if (animating) {
stopAnimation(false);
} else {
animating = true;
now = new Date().getTime();
speed = speedInput.value;
startButton.textContent = 'Cancel Animation';
// hide geoMarker
geoMarker.setStyle(null);
// just in case you pan somewhere else
map.getView().setCenter(center);
map.on('postcompose', moveFeature);
map.render();
}
}
/**
* #param {boolean} ended end of animation.
*/
function stopAnimation(ended) {
animating = false;
startButton.textContent = 'Start Animation';
// if animation cancelled set the marker at the beginning
var coord = ended ? routeCoords[routeLength - 1] : routeCoords[0];
/** #type {module:ol/geom/Point~Point} */
(geoMarker.getGeometry())
.setCoordinates(coord);
//remove listener
map.un('postcompose', moveFeature);
}
startButton.addEventListener('click', startAnimation, false);
html,
body {
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
}
.map {
height: 90%;
width: 100%;
}
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.1.3/build/ol.js"></script>
<div id="map" class="map"></div>
<label for="speed">
speed:
<input id="speed" type="range" min="10" max="999" step="10" value="60">
</label>
<button id="start-animation">Start Animation</button>
but I need to move marker smoothly between two point (or many)
See ol-ext : featureAnimation
example from that library's documentation
code snippet (example from the OpenLayers documentation using ol-ext):
// use window.onload so can move encoded polyline definition to bottom
window.onload = function() {
var route = /** #type {module:ol/geom/LineString~LineString} */ (new ol.format.Polyline({
factor: 1e6
}).readGeometry(polyline, {
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857'
}));
var routeCoords = route.getCoordinates();
var routeLength = routeCoords.length;
var routePts = new ol.Feature({
type: 'route',
geometry: new ol.geom.MultiPoint(routeCoords)
});
var routeFeature = new ol.Feature({
type: 'route',
geometry: route
});
var geoMarker = new ol.Feature({
type: 'geoMarker',
geometry: new ol.geom.Point(routeCoords[0])
});
var startMarker = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords[0])
});
var endMarker = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords[routeLength - 1])
});
var styles = {
'route': new ol.style.Style({
stroke: new ol.style.Stroke({
width: 6,
color: [237, 212, 0, 0.8]
}),
image: new ol.style.Circle({
radius: 1,
snapToPixel: false,
fill: new ol.style.Fill({
color: 'red'
})
})
}),
'icon': new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1],
src: 'https://openlayers.org/en/v4.6.5/examples/data/icon.png'
})
}),
'geoMarker': new ol.style.Style({
image: new ol.style.Circle({ // CircleStyle({
radius: 7,
snapToPixel: false,
fill: new ol.style.Fill({
color: 'black'
}),
stroke: new ol.style.Stroke({
color: 'white',
width: 2
})
})
})
};
var animating = false;
var speed, now;
var speedInput = document.getElementById('speed');
var startButton = document.getElementById('start-animation');
var vectorLayer = new ol.layer.Vector({ // VectorLayer({
source: new ol.source.Vector({ // VectorSource({
features: [routePts, routeFeature, geoMarker, startMarker, endMarker]
}),
style: function(feature) {
return styles[feature.get('type')];
}
});
var center = [-5639523.95, -3501274.52];
var map = new ol.Map({
target: document.getElementById('map'),
loadTilesWhileAnimating: true,
view: new ol.View({
center: center,
zoom: 9,
minZoom: 2,
maxZoom: 19
}),
layers: [
new ol.layer.Tile({ // TileLayer({
source: new ol.source.OSM()
}),
vectorLayer
]
});
console.log("init speed=" + speedInput.value / 100);
function startAnimation() {
if (animating) animating.stop();
console.log("update speed=" + Number(speedInput.value) / 100);
// anim.set("speed", Number(speedInput.value)/10);
var anim = new ol.featureAnimation.Path({
path: route,
easing: ol.easing.linear,
speed: Number(speedInput.value) / 100
});
animating = vectorLayer.animateFeature(geoMarker, anim);
}
geoMarker.on('change', function() {
map.getView().setCenter(geoMarker.getGeometry().getCoordinates());
});
startButton.addEventListener('click', startAnimation, false);
}
// This long string is placed here due to jsFiddle limitations.
// It is usually loaded with AJAX.
var polyline = [
'hldhx#lnau`BCG_EaC??cFjAwDjF??uBlKMd#}#z#??aC^yk#z_#se#b[wFdE??wFfE}N',
'fIoGxB_I\\gG}#eHoCyTmPqGaBaHOoD\\??yVrGotA|N??o[N_STiwAtEmHGeHcAkiA}^',
'aMyBiHOkFNoI`CcVvM??gG^gF_#iJwC??eCcA]OoL}DwFyCaCgCcCwDcGwHsSoX??wI_E',
'kUFmq#hBiOqBgTwS??iYse#gYq\\cp#ce#{vA}s#csJqaE}{#iRaqE{lBeRoIwd#_T{]_',
'Ngn#{PmhEwaA{SeF_u#kQuyAw]wQeEgtAsZ}LiCarAkVwI}D??_}RcjEinPspDwSqCgs#',
'sPua#_OkXaMeT_Nwk#ob#gV}TiYs[uTwXoNmT{Uyb#wNg]{Nqa#oDgNeJu_#_G}YsFw]k',
'DuZyDmm#i_#uyIJe~#jCg|#nGiv#zUi_BfNqaAvIow#dEed#dCcf#r#qz#Egs#{Acu#mC',
'um#yIey#gGig#cK_m#aSku#qRil#we#{mAeTej#}Tkz#cLgr#aHko#qOmcEaJw~C{w#ka',
'i#qBchBq#kmBS{kDnBscBnFu_Dbc#_~QHeU`IuyDrC_}#bByp#fCyoA?qMbD}{AIkeAgB',
'k_A_A{UsDke#gFej#qH{o#qGgb#qH{`#mMgm#uQus#kL{_#yOmd#ymBgwE}x#ouBwtA__',
'DuhEgaKuWct#gp#cnBii#mlBa_#}|Asj#qrCg^eaC}L{dAaJ_aAiOyjByH{nAuYu`GsAw',
'Xyn#ywMyOyqD{_#cfIcDe}#y#aeBJmwA`CkiAbFkhBlTgdDdPyiB`W}xDnSa}DbJyhCrX',
'itAhT}x#bE}Z_#qW_Kwv#qKaaAiBgXvIm}A~JovAxCqW~WanB`XewBbK{_A`K}fBvAmi#',
'xBycBeCauBoF}}#qJioAww#gjHaPopA_NurAyJku#uGmi#cDs[eRaiBkQstAsQkcByNma',
'CsK_uBcJgbEw#gkB_#ypEqDoqSm#eZcDwjBoGw`BoMegBaU_`Ce_#_uBqb#ytBwkFqiT_',
'fAqfEwe#mfCka#_eC_UmlB}MmaBeWkkDeHwqAoX}~DcBsZmLcxBqOwqE_DkyAuJmrJ\\o',
'~CfIewG|YibQxBssB?es#qGciA}RorAoVajA_nAodD{[y`AgPqp#mKwr#ms#umEaW{dAm',
'b#umAw|#ojBwzDaaJsmBwbEgdCsrFqhAihDquAi`Fux#}_Dui#_eB_u#guCuyAuiHukA_',
'lKszAu|OmaA{wKm}#clHs_A_rEahCssKo\\sgBsSglAqk#yvDcS_wAyTwpBmPc|BwZknF',
'oFscB_GsaDiZmyMyLgtHgQonHqT{hKaPg}Dqq#m~Hym#c`EuiBudIabB{hF{pWifx#snA',
'w`GkFyVqf#y~BkoAi}Lel#wtc#}`#oaXi_C}pZsi#eqGsSuqJ|Lqeb#e]kgPcaAu}SkDw',
'zGhn#gjYh\\qlNZovJieBqja#ed#siO{[ol\\kCmjMe\\isHorCmec#uLebB}EqiBaCg}',
'#m#qwHrT_vFps#kkI`uAszIrpHuzYxx#e{Crw#kpDhN{wBtQarDy#knFgP_yCu\\wyCwy',
'A{kHo~#omEoYmoDaEcPiuAosDagD}rO{{AsyEihCayFilLaiUqm#_bAumFo}DgqA_uByi',
'#swC~AkzDlhA}xEvcBa}Cxk#ql#`rAo|#~bBq{#``Bye#djDww#z_C_cAtn#ye#nfC_eC',
'|gGahH~s#w}#``Fi~FpnAooC|u#wlEaEedRlYkrPvKerBfYs}Arg#m}AtrCkzElw#gjBb',
'h#woBhR{gCwGkgCc[wtCuOapAcFoh#uBy[yBgr#c#iq#o#wvEv#sp#`FajBfCaq#fIipA',
'dy#ewJlUc`ExGuaBdEmbBpBssArAuqBBg}#s#g{AkB{bBif#_bYmC}r#kDgm#sPq_BuJ_',
's#{X_{AsK_d#eM{d#wVgx#oWcu#??aDmOkNia#wFoSmDyMyCkPiBePwAob#XcQ|#oNdCo',
'SfFwXhEmOnLi\\lbAulB`X_d#|k#au#bc#oc#bqC}{BhwDgcD`l#ed#??bL{G|a#eTje#',
'oS~]cLr~Bgh#|b#}Jv}EieAlv#sPluD{z#nzA_]`|KchCtd#sPvb#wSb{#ko#f`RooQ~e',
'[upZbuIolI|gFafFzu#iq#nMmJ|OeJn^{Qjh#yQhc#uJ~j#iGdd#kAp~BkBxO{#|QsAfY',
'gEtYiGd]}Jpd#wRhVoNzNeK`j#ce#vgK}cJnSoSzQkVvUm^rSgc#`Uql#xIq\\vIgg#~k',
'Dyq[nIir#jNoq#xNwc#fYik#tk#su#neB}uBhqEesFjoGeyHtCoD|D}Ed|#ctAbIuOzqB',
'_}D~NgY`\\um#v[gm#v{Cw`G`w#o{AdjAwzBh{C}`Gpp#ypAxn#}mAfz#{bBbNia#??jI',
'ab#`CuOlC}YnAcV`#_^m#aeB}#yk#YuTuBg^uCkZiGk\\yGeY}Lu_#oOsZiTe[uWi[sl#',
'mo#soAauAsrBgzBqgAglAyd#ig#asAcyAklA}qAwHkGi{#s~#goAmsAyDeEirB_{B}IsJ',
'uEeFymAssAkdAmhAyTcVkFeEoKiH}l#kp#wg#sj#ku#ey#uh#kj#}EsFmG}Jk^_r#_f#m',
'~#ym#yjA??a#cFd#kBrCgDbAUnAcBhAyAdk#et#??kF}D??OL'
].join('');
html,
body {
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
}
.map {
height: 90%;
width: 100%;
}
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.1.3/build/ol.js"></script>
<script src="https://cdn.rawgit.com/Viglino/ol-ext/master/dist/ol-ext.js"></script>
<button id="start-animation">Start Animation</button>
<label for="speed">
speed:
<input id="speed" type="range" min="10" max="4999" step="10" value="500">
</label>
<div id="map" class="map"></div>
The OpenLayers example doesn't move smoothly as the only coordinates it uses are the points where the linestring changes direction. Therefore long straights are traversed much faster than the curved sections. The ol-ext method breaks the linestring into small equal length segments, but it seems to be animating the view to keep up with the marker instead of simply animating the marker. By reusing the splitLineString function from the second part of the answer to this question https://gis.stackexchange.com/questions/306976/add-image-along-the-linestring to finely divide the linestring and setting only one icon style based on a timer based index it is possible to achieve a smooth movement independently of the view (best seen in a full screen window) and also allows the icon to stay aligned with the direction of travel.
// This long string is placed here due to jsFiddle limitations.
// It is usually loaded with AJAX.
var polyline = [
'hldhx#lnau`BCG_EaC??cFjAwDjF??uBlKMd#}#z#??aC^yk#z_#se#b[wFdE??wFfE}N',
'fIoGxB_I\\gG}#eHoCyTmPqGaBaHOoD\\??yVrGotA|N??o[N_STiwAtEmHGeHcAkiA}^',
'aMyBiHOkFNoI`CcVvM??gG^gF_#iJwC??eCcA]OoL}DwFyCaCgCcCwDcGwHsSoX??wI_E',
'kUFmq#hBiOqBgTwS??iYse#gYq\\cp#ce#{vA}s#csJqaE}{#iRaqE{lBeRoIwd#_T{]_',
'Ngn#{PmhEwaA{SeF_u#kQuyAw]wQeEgtAsZ}LiCarAkVwI}D??_}RcjEinPspDwSqCgs#',
'sPua#_OkXaMeT_Nwk#ob#gV}TiYs[uTwXoNmT{Uyb#wNg]{Nqa#oDgNeJu_#_G}YsFw]k',
'DuZyDmm#i_#uyIJe~#jCg|#nGiv#zUi_BfNqaAvIow#dEed#dCcf#r#qz#Egs#{Acu#mC',
'um#yIey#gGig#cK_m#aSku#qRil#we#{mAeTej#}Tkz#cLgr#aHko#qOmcEaJw~C{w#ka',
'i#qBchBq#kmBS{kDnBscBnFu_Dbc#_~QHeU`IuyDrC_}#bByp#fCyoA?qMbD}{AIkeAgB',
'k_A_A{UsDke#gFej#qH{o#qGgb#qH{`#mMgm#uQus#kL{_#yOmd#ymBgwE}x#ouBwtA__',
'DuhEgaKuWct#gp#cnBii#mlBa_#}|Asj#qrCg^eaC}L{dAaJ_aAiOyjByH{nAuYu`GsAw',
'Xyn#ywMyOyqD{_#cfIcDe}#y#aeBJmwA`CkiAbFkhBlTgdDdPyiB`W}xDnSa}DbJyhCrX',
'itAhT}x#bE}Z_#qW_Kwv#qKaaAiBgXvIm}A~JovAxCqW~WanB`XewBbK{_A`K}fBvAmi#',
'xBycBeCauBoF}}#qJioAww#gjHaPopA_NurAyJku#uGmi#cDs[eRaiBkQstAsQkcByNma',
'CsK_uBcJgbEw#gkB_#ypEqDoqSm#eZcDwjBoGw`BoMegBaU_`Ce_#_uBqb#ytBwkFqiT_',
'fAqfEwe#mfCka#_eC_UmlB}MmaBeWkkDeHwqAoX}~DcBsZmLcxBqOwqE_DkyAuJmrJ\\o',
'~CfIewG|YibQxBssB?es#qGciA}RorAoVajA_nAodD{[y`AgPqp#mKwr#ms#umEaW{dAm',
'b#umAw|#ojBwzDaaJsmBwbEgdCsrFqhAihDquAi`Fux#}_Dui#_eB_u#guCuyAuiHukA_',
'lKszAu|OmaA{wKm}#clHs_A_rEahCssKo\\sgBsSglAqk#yvDcS_wAyTwpBmPc|BwZknF',
'oFscB_GsaDiZmyMyLgtHgQonHqT{hKaPg}Dqq#m~Hym#c`EuiBudIabB{hF{pWifx#snA',
'w`GkFyVqf#y~BkoAi}Lel#wtc#}`#oaXi_C}pZsi#eqGsSuqJ|Lqeb#e]kgPcaAu}SkDw',
'zGhn#gjYh\\qlNZovJieBqja#ed#siO{[ol\\kCmjMe\\isHorCmec#uLebB}EqiBaCg}',
'#m#qwHrT_vFps#kkI`uAszIrpHuzYxx#e{Crw#kpDhN{wBtQarDy#knFgP_yCu\\wyCwy',
'A{kHo~#omEoYmoDaEcPiuAosDagD}rO{{AsyEihCayFilLaiUqm#_bAumFo}DgqA_uByi',
'#swC~AkzDlhA}xEvcBa}Cxk#ql#`rAo|#~bBq{#``Bye#djDww#z_C_cAtn#ye#nfC_eC',
'|gGahH~s#w}#``Fi~FpnAooC|u#wlEaEedRlYkrPvKerBfYs}Arg#m}AtrCkzElw#gjBb',
'h#woBhR{gCwGkgCc[wtCuOapAcFoh#uBy[yBgr#c#iq#o#wvEv#sp#`FajBfCaq#fIipA',
'dy#ewJlUc`ExGuaBdEmbBpBssArAuqBBg}#s#g{AkB{bBif#_bYmC}r#kDgm#sPq_BuJ_',
's#{X_{AsK_d#eM{d#wVgx#oWcu#??aDmOkNia#wFoSmDyMyCkPiBePwAob#XcQ|#oNdCo',
'SfFwXhEmOnLi\\lbAulB`X_d#|k#au#bc#oc#bqC}{BhwDgcD`l#ed#??bL{G|a#eTje#',
'oS~]cLr~Bgh#|b#}Jv}EieAlv#sPluD{z#nzA_]`|KchCtd#sPvb#wSb{#ko#f`RooQ~e',
'[upZbuIolI|gFafFzu#iq#nMmJ|OeJn^{Qjh#yQhc#uJ~j#iGdd#kAp~BkBxO{#|QsAfY',
'gEtYiGd]}Jpd#wRhVoNzNeK`j#ce#vgK}cJnSoSzQkVvUm^rSgc#`Uql#xIq\\vIgg#~k',
'Dyq[nIir#jNoq#xNwc#fYik#tk#su#neB}uBhqEesFjoGeyHtCoD|D}Ed|#ctAbIuOzqB',
'_}D~NgY`\\um#v[gm#v{Cw`G`w#o{AdjAwzBh{C}`Gpp#ypAxn#}mAfz#{bBbNia#??jI',
'ab#`CuOlC}YnAcV`#_^m#aeB}#yk#YuTuBg^uCkZiGk\\yGeY}Lu_#oOsZiTe[uWi[sl#',
'mo#soAauAsrBgzBqgAglAyd#ig#asAcyAklA}qAwHkGi{#s~#goAmsAyDeEirB_{B}IsJ',
'uEeFymAssAkdAmhAyTcVkFeEoKiH}l#kp#wg#sj#ku#ey#uh#kj#}EsFmG}Jk^_r#_f#m',
'~#ym#yjA??a#cFd#kBrCgDbAUnAcBhAyAdk#et#??kF}D??OL'
].join('');
function splitLineString(geometry, minSegmentLength, options) {
function calculatePointsDistance(coord1, coord2) {
var dx = coord1[0] - coord2[0];
var dy = coord1[1] - coord2[1];
return Math.sqrt(dx * dx + dy * dy);
};
function calculateSplitPointCoords(startNode, nextNode, distanceBetweenNodes, distanceToSplitPoint) {
var d = distanceToSplitPoint / distanceBetweenNodes;
var x = nextNode[0] + (startNode[0] - nextNode[0]) * d;
var y = nextNode[1] + (startNode[1] - nextNode[1]) * d;
return [x, y];
};
function calculateAngle(startNode, nextNode, alwaysUp) {
var x = (startNode[0] - nextNode[0]);
var y = (startNode[1] - nextNode[1]);
var angle = Math.atan(x/y);
if (!alwaysUp) {
angle = y > 0 ? angle + Math.PI : x < 0 ? angle + Math.PI*2 : angle;
}
return angle;
};
var splitPoints = [];
var coords = geometry.getCoordinates();
var coordIndex = 0;
var startPoint = coords[coordIndex];
var nextPoint = coords[coordIndex + 1];
var angle = calculateAngle(startPoint, nextPoint, options.alwaysUp);
var n = Math.ceil(geometry.getLength()/minSegmentLength);
var segmentLength = geometry.getLength() / n;
var currentSegmentLength = options.midPoints ? segmentLength/2 : segmentLength;
for (var i = 0; i <= n; i++) {
var distanceBetweenPoints = calculatePointsDistance(startPoint, nextPoint);
currentSegmentLength += distanceBetweenPoints;
if (currentSegmentLength < segmentLength) {
coordIndex++;
if(coordIndex < coords.length - 1) {
startPoint = coords[coordIndex];
nextPoint = coords[coordIndex + 1];
angle = calculateAngle(startPoint, nextPoint, options.alwaysUp);
i--;
continue;
} else {
if (!options.midPoints) {
var splitPointCoords = nextPoint;
splitPointCoords.push(angle);
splitPoints.push(splitPointCoords);
}
break;
}
} else {
var distanceToSplitPoint = currentSegmentLength - segmentLength;
var splitPointCoords = calculateSplitPointCoords(startPoint, nextPoint, distanceBetweenPoints, distanceToSplitPoint);
startPoint = splitPointCoords.slice();
splitPointCoords.push(angle);
splitPoints.push(splitPointCoords);
currentSegmentLength = 0;
}
}
return splitPoints;
};
var route = new ol.format.Polyline({
factor: 1e6
}).readGeometry(polyline, {
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857'
});
var routeCoords = route.getCoordinates();
var routeLength = routeCoords.length;
var routeFeature = new ol.Feature({
type: 'route',
geometry: route
});
var startMarker = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords[0])
});
var endMarker = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords[routeLength - 1])
});
var raster = new ol.layer.Tile({
source: new ol.source.OSM()
});
var repeat = false;
var timer = -1;
var style = function(feature, resolution) {
if (feature.get('type') == 'route') {
var styles = [
new ol.style.Style({
stroke: new ol.style.Stroke({
width: 6,
color: [237, 212, 0, 0.8],
})
})
];
if (timer < 0) {
feature.unset('splitPoints', true);
} else {
var splitPoints = feature.get('splitPoints');
if (!splitPoints) {
splitPoints = splitLineString(feature.getGeometry(), 2 * resolution, {alwaysUp: false, midPoints: true});
feature.set('splitPoints', splitPoints, true);
}
if (!repeat && timer >= splitPoints.length) {
stopAnimation(true);
} else {
var index = timer % splitPoints.length;
var point = splitPoints[index];
styles.push(
new ol.style.Style({
geometry: new ol.geom.Point([point[0],point[1]]),
image: new ol.style.Icon({
src: 'https://cdn1.iconfinder.com/data/icons/basic-ui-elements-color-round/3/19-32.png',
rotation: point[2]
}),
zIndex: 1
})
);
}
}
return styles;
} else {
return [
new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1],
src: 'https://openlayers.org/en/v4.6.5/examples/data/icon.png',
})
})
];
}
}
var vector = new ol.layer.Vector({
source: new ol.source.Vector({
features: [routeFeature, startMarker, endMarker]
}),
style: style
});
var map = new ol.Map({
layers: [raster, vector],
target: 'map',
view: new ol.View({
center: [-5639523.95, -3501274.52],
zoom: 10
})
});
var animating = false;
var token;
var speedInput = document.getElementById('speed');
var startButton = document.getElementById('start-animation');
function startAnimation() {
if (animating) {
stopAnimation(false);
} else {
animating = true;
startButton.textContent = 'Cancel Animation';
token = setInterval( function(){
timer++;
vector.setStyle(style);
}, 6000/speedInput.value);
}
}
function stopAnimation(ended) {
animating = false;
startButton.textContent = 'Start Animation';
clearInterval(token);
timer = -1;
if (!ended) {
vector.setStyle(style);
}
}
startButton.addEventListener('click', startAnimation, false);
html,
body {
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
}
.map {
height: 90%;
width: 100%;
}
<link rel="stylesheet" href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" type="text/css">
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<div id="map" class="map"></div>
<label for="speed">
speed:
<input id="speed" type="range" min="10" max="999" step="10" value="200">
</label>
<button id="start-animation">Start Animation</button>

Openlayer Linestring with two or more lines on the one or more coordinates?

My problem is the following:
Linestring with two or more lines on the one or more coordinates.
With several LineStrings only one string is shown in the map, I know, the LineStrings also use the same coordinates.
Is there a way that the LineStrings can use one and the same coordinates, but a shift of the line (to have for example three lines next to each other) takes place in the structure of the map?
The code is a bit better, which explains a lot:
var logoElement = document.createElement ('a');
logoElement.href = 'http://www.schienenpost.de/';
logoElement.target = '_blank';
var logoImage = document.createElement ('img');
logoImage.src = 'http://www.schienenpost.de/schienenpost.png';
logoElement.appendChild (logoImage);
var iconStyle = new ol.style.Style ({
image: new ol.style.Icon (/** #type {olx.style.IconOptions} */ ({
anchor: [0.5, 1.0],
src: 'http://www.schienenpost.de/marker/marker.png'
}))
});
var scaleLineControl = new ol.control.ScaleLine();
var markerStartEnd = function (layer,feature) {
var i, iconFeature, iconFeatures = [], coordinates, nameLine;
coordinates = feature.getGeometry ().getCoordinates ();
nameLine = feature.getProperties () ['name'];
i = 0;
iconFeature = new ol.Feature ({
geometry: new ol.geom.Point (coordinates[i]),
name: 'Start'+nameLine,
});
iconFeature.setStyle (iconStyle);
iconFeatures.push (iconFeature);
i = coordinates.length - 1;
iconFeature = new ol.Feature ({
geometry: new ol.geom.Point (coordinates[i]),
name: 'End'+nameLine,
});
iconFeature.setStyle (iconStyle);
iconFeatures.push (iconFeature);
layer.getSource ().addFeatures (iconFeatures);
};
var layerLines = new ol.layer.Vector ({
source: new ol.source.Vector ({
format: new ol.format.GeoJSON (),
url: 'schienenpost.geojson',
useSpatialIndex: false
}),
style: new ol.style.Style ({stroke: new ol.style.Stroke ({color : 'red', width: 3})}),
});
var layerMarker = new ol.layer.Vector ({
title: 'Marker',
source: new ol.source.Vector ()
});
var element = document.getElementById ('popup');
var popup = new ol.Overlay ({
element: element,
positioning: 'bottom-center',
stopEvent: false,
offset: [0, -0]
});
var map = new ol.Map ({
controls: ol.control.defaults ()
.extend ([
new ol.control.OverviewMap (),
new ol.control.FullScreen (),
scaleLineControl
]),
//target 'map',
target: document.getElementById ('map'),
layers: [
new ol.layer.Tile ({
source: new ol.source.OSM ()
}),
],
view: new ol.View ({
center: ol.proj.fromLonLat ([10.627, 53.620]),
zoom: 8
}),
logo: logoElement
});
map.addOverlay (popup);
map.addLayer (layerLines);
map.addLayer (layerMarker);
map.once ('moveend', function(e) {
layerLines.getSource ().getFeaturesCollection ().forEach (function (feature) {
markerStartEnd (layerMarker,feature);
});
});
map.on ('click', function (evt) {
var feature = map.forEachFeatureAtPixel (evt.pixel,
function (feature) {
return feature;
});
if (feature) {
var coordinates = feature.getGeometry ().getCoordinates ();
var clickpoint = map.getCoordinateFromPixel (evt.pixel);
if (!isNaN (coordinates [0])) { // Punkt
popup.setPosition (coordinates);
} else if (!isNaN (coordinates [0][0])) { // Linie
popup.setPosition (clickpoint);
} else { // kein brauchbares feature
$ (element).popover ('destroy');
return;
}
$ (element).popover ({
'placement': 'top',
'html': true,
'content': feature.get ('name')
});
$ (element).popover ().data ('bs.popover').options.content = feature.get ('name');
$ (element).popover ('show');
} else {
$ (element).popover ('hide');
}
});
map.on ('pointermove', function(e) {
if (e.dragging) {
$ (element).popover ('destroy');
return;
}
var pixel = map.getEventPixel (e.originalEvent);
var hit = map.hasFeatureAtPixel (pixel);
map.getTarget ().style.cursor = hit ? 'pointer' : '';
});

Leaflet.js: click a polygon to remove the the layer and change it to new one

I've been making a Leaflet map for a while and trying to figure out the way to make it so if I click one of the polygon in GeoJSON layer, it will remove the current layer and replace it with another layer.
Likewise, if I click it again, it will remove the new layer and replace it with previous layer.
I've been trying to tinker with different stuff but nothing works. This is one of my recent attempt.
<script type="text/javascript" src="provinces.js"></script>
<script>
var map = L.map('map').setView([-2.5, 119], 5);
L.tileLayer('http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap © CartoDB',
subdomains: 'abcd',
maxZoom: 19
}).addTo(map);
// get color depending on population density value
function getColor(d) {
return d > 5000 ? '#800026' :
d > 2500 ? '#BD0026' :
d > 1000 ? '#E31A1C' :
d > 500 ? '#FC4E2A' :
'#FFEDA0';
}
function style(feature) {
return {
weight: 2,
opacity: 1,
color: 'white',
dashArray: '',
fillOpacity: 0.7,
fillColor: getColor(feature.properties.kode)
};
}
function highlightFeature(e) {
var layer = e.target;
layer.setStyle({
weight: 5,
color: '#ccc',
dashArray: '',
fillOpacity: 0.7
});
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
layer.bringToFront();
}
info.update(layer.feature.properties);
}
var geojson;
function resetHighlight(e) {
geojson.resetStyle(e.target);
info.update();
}
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
function addNewBoundary(e) { // this function doesn't do anything
var newLayerBoundary = new L.geoJson();
newLayerBoundary.addTo(map);
$.ajax({
dataType: "json",
url: "province-details.geojson",
success: function(data) {
$(data.features).each(function(key, data) {
newLayerBoundary.addData(data);
});
}
}).error(function() {});
}
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: clearLayers // with this it just clears the layers before being clicked
});
}
geojson = L.geoJson(provinces, {
style: style,
onEachFeature: onEachFeature
}).addTo(map);
</script>
var layers = [firstLayer,secondLayer]
function switchLayers(){
if(map.haslayer(layers[firstLayer])){
map.addLayer(layers[secondLayer]);
map.removeLayer(layers[firstLayer]);
}else{
if(map.haslayer(layers[secondLayer])){
map.addLayer(layers[firstLayer]);
map.removeLayer(layers[secondLayer]);
}
}