Leaflet 0.7.7 Zoombased layer switching separated by theme - zooming

We are working on a school project where the aim is to create a map in which based on the amount of zoom the layer switches from one aggregate level to a smaller aggregate level. Additionally, we have several groups of layers based on a theme for which this needs to apply. So you'd click on a theme and a new group of layers that switches based on zoom level become active and when you click another theme another group of layers become active and switch based on zoom level. This means that the themes are exclusionary, ideally you can't have more than one theme active at a time.
We tried to make this work in several ways already but without much success. Using the L.Control.Layers we were unable to group different layers together under one radio button and have them switch based on zoom since the layer control build into leaflet always splits them up into separate ones. Even using L.layerGroup to combine several layer variables or creating several layers into one variable and then adding them to the map using l.control.layer.
We also tried to use L.easyButton (https://github.com/CliffCloud/Leaflet.EasyButton). This allowed us to put the variables under one button and add a zoom based layer switching inside of it. However, the issue here is that we are unable to deactivate the functionality once activated. Which results in several of them being active at one point and overlapping each other.
If possible we would like to know if we should use a different approach or if either the leaflet control function or the use of easyButton could work and how?
This is example code for one of the buttons, which would appear several times but show a different theme:
L.easyButton( '<span class="star">&starf;</span>', function (polygon) {
var ejerlav_polygon = new L.tileLayer.betterWms(
'http://[IP]:[PORT]/geoserver/prlayer/wms', {
layers: 'prlayer:ejerlav',
transparent: true,
styles: 'polygon',
format: 'image/png'});
var municipality_polygon = new L.tileLayer.betterWms(
'http://[IP]:[PORT]/geoserver/prlayer/wms', {
layers: 'prlayer:municipality',
transparent: true,
styles: 'polygon',
format: 'image/png'});
map.on("zoomend", function() {
if (map.getZoom() <= 10 && map.getZoom() >= 2) {
map.addLayer(municipality_polygon);
} else if (map.getZoom() > 10 || map.getZoom() < 2) {
map.removeLayer(municipality_polygon);
}
});
map.on("zoomend", function() {
if (map.getZoom() <= 11 && map.getZoom() >= 11) {
map.addLayer(ejerlav_polygon);
} else if (map.getZoom() > 11 || map.getZoom() < 11) {
map.removeLayer(ejerlav_polygon);
}
});
}).addTo(map);

If my understanding is correct, you would like to give the user the ability to switch between "themes" (some sort of group of layers that switch themselves based on the map current zoom level), possibly using Leaflet Layers Control?
And regarding the switch based on map zoom, you cannot just change the Tile Layer template URL because you use some WMS?
As for the latter functionality (switching layers within a group / theme based on map zoom), a "simple" solution would be to create your own type of layer that will listen to map "zoomend" event and change the Tile Layer WMS accordingly.
L.LayerSwitchByZoom = L.Class.extend({
initialize: function (layersArray) {
var self = this;
this._layersByZoom = layersArray;
this._maxZoom = layersArray.length - 1;
this._switchByZoomReferenced = function () {
self._switchByZoom();
};
},
onAdd: function (map) {
this._map = map;
map.on("zoomend", this._switchByZoomReferenced);
this._switchByZoom();
},
onRemove: function (map) {
map.off("zoomend", this._switchByZoomReferenced);
this._removeCurrentLayer();
this._map = null;
},
addTo: function (map) {
map.addLayer(this);
return this;
},
_switchByZoom: function () {
var map = this._map,
z = Math.min(map.getZoom(), this._maxZoom);
this._removeCurrentLayer();
this._currentLayer = this._layersByZoom[z];
map.addLayer(this._currentLayer);
},
_removeCurrentLayer: function () {
if (this._currentLayer) {
map.removeLayer(this._currentLayer);
}
}
});
You would then instantiate that layer "theme" / group by specifying an array of layers (your Tile Layers WMS), where the array index corresponds to the zoom level at which that Tile Layer should appear.
var myLayerSwitchByZoomA = new L.LayerSwitchByZoom([
osmMapnik, // zoom 0, osmMapnik is a Tile Layer or any other layer
osmDE, // zoom 1
osmFR, // zoom 2
osmHOT // zoom 3, etc.
]);
Once this new layer type is set, you can use it in the Layers Control like any other type of Layer / Tile Layer, etc.
L.control.layers({
"OpenStreetMap": myLayerSwitchByZoomA,
"ThunderForest": myLayerSwitchByZoomB
}).addTo(map);
Demo: http://jsfiddle.net/ve2huzxw/85/
Note that you could further improve the implementation of L.LayerSwitchByZoom to avoid flickering when changing the layer after zoom end, etc.

Related

How to check if a marker is in a cluster?

With Leaflet.Markercluster, how is it possible to check if a marker is in a cluster?
Use Leaflet's hasLayer() function
Regular visible markers technically exist as layers in the map. Leaflet also has a function hasLayer() that returns true/false whether a given reference, like a stored marker, currently exists (ie "is visible") in the map as a layer.
For my specific problem, I gave a special visual decoration to a selected map marker (e.g. red border), and this decoration remained even after that marker went in-and-out of a cluster. I needed the selected marker to deselect itself if it entered a cluster.
Below is rough logic that allows me to check whether a given marker reference is visible to the user after a cluster event (that may occur for any reason, like zoom events or non-user driven movements in the map).
Relevant abbreviated code:
I hold my clustering like this...
this.markersClustered = L.markerClusterGroup({ // Lots of stuff here... } })
In my click event for individual markers, I store the marker in a "selectedItem" object (which I use for all kinds of other logic in my app)...
onEachFeature(feature, marker) {
marker.on("click", e => {
// ... (lots of code) ...
// Save instance of the marker; I call it "layer" only
// because I only use this for the hasLayer() check
this.selectedItem.layer = marker
// Here do other stuff too, whatever you need...
//this.selectedItem.target = e.target
//this.selectedItem.latlng = e.latlng
//this.selectedItem.id = e.target.feature.id
//this.selectedItem.icon = layer._icon
// ... (lots of code) ...
})
}
When cluster animations end, using hasLayer(), check whether the selected marker is in a cluster, and do stuff.
Note: the check is done at the END of clustering animation in my use case, because I didn't want the user to see the selected marker lose it's special visual decoration while the clustering animation was happening (it would appear buggy).
this.markersClustered.on('animationend', (e) => {
// Check visibility of selected marker
if(this.map.hasLayer(this.selectedItem.layer)) {
// True: Not in a cluster (ie marker is visible on map)
} else {
// False: In a cluster (ie marker is not visible on map)
}
console.log("Selected marker visible?:" + this.map.hasLayer(this.selectedItem.layer))
})

PhaserJS collision tilemap not working

I have a tilemap that only 1 layer is correctly colliding with the player. Been through all the examples, but i can't seem to get it working on multiple layers.
I have 1 tilemap that contains all the json data for a total of 13 layers, but for the example i have only included 3.
I would like for the player to collide with different layers and have different callbacks, e.g. cannot walk through, pick up item if within range etc. but all using 1 spritemap/tilemap.
var game = new Phaser.Game(1200, 780, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render });
function preload() {
this.load.tilemap('main_map', 'img here', null, Phaser.Tilemap.TILED_JSON);
this.load.image('sprite_map', 'img here');
this.load.image('player_image', 'img here');
}
var map;
var tileset;
var bLayer;
var wLayer;
var player;
var sLayer;
var cursors;
function create() {
game.physics.startSystem(Phaser.Physics.ARCADE);
// initiallize the tilemap
map = game.add.tilemap('main_map');
map.addTilesetImage('otherNew', 'sprite_map');
//draw the layers
bLayer = map.createLayer(0);
wLayer = map.createLayer(1);
sLayer = map.createLayer(2);
wLayer.resizeWorld();
player = game.add.sprite(600, 600, 'player_image');
game.physics.arcade.enable(player);
player.body.collideWorldBounds = true; // works
//game camera and movment keys here
}
function update() {
game.physics.arcade.collide(player, wLayer); // DOES NOT WORK
game.physics.arcade.collide(player, sLayer); // THIS WORKS
map.setCollision(1, true, wLayer); // DOES NOT WORK
map.setCollision(2, true, sLayer); // THIS WORKS
//movement here already works so didn't include
}
I'm thinking that your wLayer.resizeWorld(); function makes your tilemap's width/height the size of the world. Looks like this is the only difference between wLayer and sLayer.
Setting width/height in Phaser does not automatically resize the collision body. To do that, use the setSize function.
To view your current body, use Phaser's debug methods
this.game.debug.body(someSprite, 'rgba(255,0,0,0.5)');
The answer is that each layer has its own set of tilemaps, and as i was using a spritesheet to generate each layer, those layers had some value that needed to be set. I didn't have the patience to check what actual collision layer I needed, but as I had split them all up into separate logical layers I just set between 0, 100 for each.
// In the create section
map.setCollisionBetween(0, 100,true, wLayer,true);
map.setCollisionBetween(0, 100,true, sLayer,true);

Asynchronous Entity Rendering in Cesium 1.7+

Good Day all,
I've been working with Cesium for a bit now and I started when Primitive Collections were the thing to use. I had click and drag primitive rendering working, but now I want to upgrade Cesium and move on to entities. I moved over the code, refactored, and can click and drag to draw shapes; however, before I was able to flip the asynchronous flag and it would render as I moved the mouse. Now, I'm unable to do that. I tried setting 'allowDataSourcesToSuspendAnimation' on the viewer to false, but to no avail. Any help would be extremely appreciated.
In my naivety I forgot to add a code snippet to my question. This is in a clock tick event listener that only fires when mouse down is happening(Boolean value set to true)
var radius = Cesium.Cartesian3.distance(cartesianStartMousePosition, cartesianMousePosition);
if (radius > 0) {
if (currentEntity && currentEntity.id) {
currentEntity.position = cartesianStartMousePosition;
currentEntity.ellipse = {
semiMinorAxis: radius,
semiMajorAxis: radius,
material: new Cesium.ColorMaterialProperty(myColor)
};
currentEntity.label = {
text: 'New Overlay',
scale: 0.35
};
overlayEntities.resumeEvents();
}
else {
currentEntity = new Cesium.Entity({
position: cartesianStartMousePosition,
ellipse: {
semiMinorAxis: radius,
semiMajorAxis: radius,
material: new Cesium.ColorMaterialProperty(myColor)
},
label: {
text: 'New Overlay',
scale: 0.35
},
isSaved: false
});
overlayEntities.add(currentEntity);
}
bDrewPrim = true;
}
It looks to me like you're doing too much work to update the entity. You only need to set the values that have changed, and you should only do that if the change was substantial enough to warrant a graphics update. Try replacing the top half of your if statement with something like this:
var lastRadius = 0;
...
if (radius > 0 && !Cesium.Math.equalsEpsilon(radius, lastRadius, Cesium.Math.EPSILON2)) {
lastRadius = radius;
if (currentEntity && currentEntity.id) {
currentEntity.ellipse.semiMinorAxis = radius;
currentEntity.ellipse.semiMajorAxis = radius;
} else {
// Same as before...
I believe the ellipsoid primitive is being built on a worker thread, so this code tries to avoid setting the new radius every tick unless a real change has been applied to it.
Also, you don't show your mouse down handler, but make sure that you're setting this flag, if you aren't already setting it:
viewer.scene.screenSpaceCameraController.enableInputs = false;
This stops the globe from spinning while you drag-select the ellipse. You can reset this to true on mouse up.

How to structure AngularJS and PaperJS project

The idea is to make use of Angular in a simple canvas game development. In theory the project should benefit from being more systematic, manageable and scalable. This is not a sprite/tile/collision game and PaperJS is used to do most canvas drawing and interactions.
What is the best approach to integrate Paper.js (or other canvas drawing library) into multiple NG views, in order to have each view representing a game stage?
Is it possible to setup Paper once and use paper across multiple views?
The game allows user to revisit previous stages/views. Do I have to re-setup Paper every time the view/canvas loads? (Shown in example below, if only setup once a blank canvas will appear on view's second visit)
How do I transfer Paper js information between views? e.g. captures user drawing in view 1, and display drawing in view 3.
Background:
Paper JS
I'm working on a project to create a simple canvas game with 4 stages. I decided to use PaperJS for its advantage for drawing and animating shapes. Content and ui for each stage is kept in a separate layer within the same paper project.
Angular JS
The game has become more complicated as it develops. After some research, I decided to use Angular to organise the whole game, although I'm new to Angular. The plan:
The 4 game stages are split into four views, each has its own canvas
Custom directives are used to setup paper on each canvas
Using service for communication between canvases. For example allow users to draw in stage one and display drawing in stage two
I have made a mock up in plunker showing the basic setup and animation with Paper.js. Each canvas sits in a separate view with routing enabled.
Plunker demo: http://plnkr.co/edit/Um1jTp8xTmAzVEdzk2Oq?p=preview
For testing sake, I have made
paper.project.layers[0].children
visible anytime. After a paper is setup, firing "add shapes" button would introduce children to the active layer as expected.
Problem 1 (Draw1 in demo)
In DRAW1, paper will only setup once on the canvas when the view first loads:
drawControllers.directive('drawingBoard',['drawService',function(drawService){
function link(scope, element, attrs){
// setup Paper
var canvas = element[0];
if ( scope.objectValue.count < 1){
paper = new paper.PaperScope();
paper.setup(canvas);
scope.setCount( scope.objectValue.count + 1 );
with (paper) {
var shape = new Shape.Circle(new Point(200, 200), 200);
shape.strokeColor = 'black';
shape.fillColor = 'yellow';
// Display data in canvas
var text = new PointText(new Point(20, 20));
text.justification = 'left';
text.fillColor = 'black';
var text2 = new PointText(new Point(200, 200));
text2.justification = 'center';
text2.fillColor = 'black';
text2.content = 'click to change size';
shape.onClick = function(event) {
this.fillColor = 'orange';
scope.$apply(function () {
scope.setWidth(Math.round((Math.random()*100)+100));
});
}
view.onFrame = function(event) {
if ( text.position.x > 440 ){
text.position.x = -40;
} else {
text.position.x = text.position.x + 3;
}
text.content = 'Shape width: ' + scope.objectValue.width;
shape.radius = scope.objectValue.width;
scope.$apply(function () {
scope.setMessage();
});
}
paper.view.draw();
}
} else {
scope.setMessage();
}
}
return {
link: link
}
}]);
However, if navigate from DRAW1 to HOME and back to DRAW1, the canvas would appear blank. But firing "add shapes" at this point would still create new children to the layer.
Problem 2 (DRAW2 in demo)
By removing this line
if ( scope.objectValue.count < 1){
// ... paper setup ...
}
Paper will setup in DRAW2 every time it loads.
But that introduces a new paper project every time.
Thank you
Thank you for any advice and suggestions.
You are creating new paper project inside the link function, which runs every time for every new use of your directive, in particular, every time a new view is generated.
If you want it to run only once, you can put it in the directive's compile function, or inside a dedicated Angular service or don't close/re-open the view.
The latter can be done simply using ng-show/ng-hide keeping the views inside your DOM instead of changing the routes: https://stackoverflow.com/a/26820013/1614973.

leaflet control geojson layers by zoom level

just looking for a way to control geojson layers by zoomlevel like minZoom , maxZoom for tileLayers, any ideas?
Descrition: geojson points layer, different classes of points, say Nation capitol, Province Capitol, Department, capitol, rest of simple towns.
Goal: at zoom level 4 display only Nation capitol (one only piont)
at zoom level 6 diplay all of 24 province capitols (you will only see a part of them)
at zoom level 8 display 524 departments capitols (you will only see a part of them)
at zoom level 10 display rest of towns, same idea.
thanks in advance.
JC
Check out Map events in the Leaflet Docs.
You can subscribe to the 'zoomend' event as well as 'zoomstart' and a few others
map.on('zoomend', function (e) {
myZoomHandler();
});
In your zoom handler function, loop over your layers and add or remove layers as needed.
function myZoomHandler() {
var currentZoom = map.getZoom();
switch (currentZoom) {
case 4:
//show Capitols
break;
case 6:
//show Provinces
break;
default:
// etc
break;
}
}
Leaflet has a viewereset event that is triggered when the map updates (on load, on zoom).
You can do something like:
map.on('viewreset', function() {
var zoom = this.getZoom();
// show/hide layers based on zoom level
}, map);
One way to show/hide layers is through L.GeoJSON's setStyle() method (use display or visibility properties).