I'm trying to implement a zoom bar in mapbox gl js but the only thing I found is this code from their documentation which adds an +, - and a reset. Is there anyway I can add a slider zoom level bar ? (A bar like this https://www.w3schools.com/howto/howto_js_rangeslider.asp )
var nav = new mapboxgl.NavigationControl();
map.addControl(nav, 'bottom-left');
Yes, you can, it requires to create a custom control and add manually the event to update the map zoom... but it's only a few lines of code. I didn't work too much in the css styling.
here's a fiddle I have created with an example How to create a custom zoom control
And here's the relevant scripting code
<script>
mapboxgl.accessToken = 'PUT HERE YOUR MAPBOX TOKEN';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11', // stylesheet location
zoom: 3, // starting zoom
center: [-95, 40], // starting position [lng, lat]
});
map.on('load', function () {
let zoomControl = new CustomZoomControl();
map.addControl(zoomControl, 'top-right');
map.on('zoom', function () {
zoomControl.update();
});
});
class CustomZoomControl {
onAdd(map) {
this.map = map;
this.container = document.createElement('div');
this.container.className = " mapboxgl-ctrl mapboxgl-ctrl-group";
this.input = document.createElement('input');
this.input.type = "range"
this.input.min = 1;
this.input.max = 220;
this.createAttribute(this.input , "value", map.getZoom()*10)
this.input.className = "slider";
this.input.id = "myRange";
this.container.appendChild(this.input);
// Update the current slider value (each time you drag the slider handle)
this.input.oninput = function () {
map.setZoom(this.value/10);
}
return this.container;
}
onRemove() {
this.container.parentNode.removeChild(this.container);
this.map = undefined;
}
createAttribute(obj, attrName, attrValue) {
var att = document.createAttribute(attrName);
att.value = attrValue;
obj.setAttributeNode(att);
}
update() {
let zoom = map.getZoom() * 10;
if (this.input.value != zoom) this.input.value = zoom;
}
}
</script>
Related
I want to display cesium clock and timeline widget inside a different div container outside of the cesium container. With the help of this link I've created a separate clock widget and applied animation on an entity. Animation is working but the clock widget is not working. It seems like default clock widget is working not the newly created. Sandcastle link
var viewer = new Cesium.Viewer("cesiumContainer", {
infoBox: false, //Disable InfoBox widget
selectionIndicator: false, //Disable selection indicator
shouldAnimate: true, // Enable animations
terrainProvider: Cesium.createWorldTerrain(),
});
//Enable lighting based on the sun position
viewer.scene.globe.enableLighting = true;
//Enable depth testing so things behind the terrain disappear.
viewer.scene.globe.depthTestAgainstTerrain = true;
var _currentSysDT = new Date();
function onTimelineScrubfunction(e) {
clock = e.clock;
clock.currentTime = e.timeJulian;
clock.shouldAnimate = false;
}
var timeControlsContainer = document.getElementById('timeControlsContainer');
// viewer.animation.viewModel.timeFormatter = LocalFormatter;
var clock = new Cesium.Clock();
clock.startTime = Cesium.JulianDate.fromDate(_currentSysDT);
clock.currentTime = Cesium.JulianDate.fromDate(_currentSysDT);
clock.clockRange = Cesium.ClockRange.LOOP_STOP;
var clockViewModel = new Cesium.ClockViewModel(clock);
clockViewModel.startTime = Cesium.JulianDate.fromDate(_currentSysDT);
clockViewModel.currentTime = Cesium.JulianDate.fromDate(_currentSysDT);
var animationContainer = document.createElement('div');
animationContainer.className = 'cesium-viewer-animationContainer';
timeControlsContainer.appendChild(animationContainer);
var animViewModel = new Cesium.AnimationViewModel(clockViewModel);
var animation = new Cesium.Animation(animationContainer, animViewModel);
var timelineContainer = document.createElement('div');
timelineContainer.className = 'cesium-viewer-timelineContainer';
timeControlsContainer.appendChild(timelineContainer);
var timeline = new Cesium.Timeline(timelineContainer, clock);
timeline.addEventListener('settime', onTimelineScrubfunction, false);
timeline.zoomTo(clock.startTime, clock.stopTime);
clockViewModel.shouldAnimate = true;
animViewModel.snapToTicks = false;
animViewModel.pauseViewModel.command(); //comment this for default play
timeline.zoomTo(clock.startTime, Cesium.JulianDate.addSeconds(clock.startTime, 60, new Cesium.JulianDate()));
clock.onTick.addEventListener(function (clock) {
});
window.setInterval(function () {
clock.tick();
}, 32);
var start = Cesium.JulianDate.addSeconds(Cesium.JulianDate.fromDate(_currentSysDT), 0, new Cesium.JulianDate());
var stop = Cesium.JulianDate.addSeconds(Cesium.JulianDate.fromDate(_currentSysDT), 120, new Cesium.JulianDate());
var positions = [{"lat":"23.14291673","lon":"73.60544359","alt":"33.79739465869949"},{"lat":"23.14291736","lon":"73.60558935","alt":"33.705623697852786"},{"lat":"23.14284330","lon":"73.60553133","alt":"33.280035949546644"},{"lat":"23.14284640","lon":"73.60546898","alt":"33.219775982790594"}];
var positionProperty = new Cesium.SampledPositionProperty();
positions.forEach((p,i)=>{
let _pos = Cesium.Cartesian3.fromDegrees(parseFloat(p.lon), parseFloat(p.lat), parseFloat(p.alt));
let _time = Cesium.JulianDate.addSeconds(start, i, new Cesium.JulianDate());
positionProperty.addSample(_time, _pos);
});
var entity = viewer.entities.add({
//Set the entity availability to the same interval as the simulation time.
availability: new Cesium.TimeIntervalCollection([
new Cesium.TimeInterval({
start: start,
stop: stop,
}),
]),
//Use our computed positions
position: positionProperty,
//Automatically compute orientation based on position movement.
orientation: new Cesium.VelocityOrientationProperty(positionProperty),
//Load the Cesium plane model to represent the entity
model: {
uri: "../SampleData/models/CesiumMan/Cesium_Man.glb",
minimumPixelSize: 64,
},
//Show the path as a yellow line sampled in 1 second increments.
path: {
resolution: 1,
material: new Cesium.PolylineGlowMaterialProperty({
glowPower: 0.1,
color: Cesium.Color.YELLOW,
}),
width: 10,
},
});
viewer.trackedEntity = entity;
You need to create the viewer after the clockViewModel, and pass in the clockViewModel as a constructor option. For example:
var viewer = new Cesium.Viewer("cesiumContainer", {
infoBox: false, //Disable InfoBox widget
selectionIndicator: false, //Disable selection indicator
terrainProvider: Cesium.createWorldTerrain(),
// Construct this viewer using your previously constructed clockViewModel.
clockViewModel: clockViewModel,
// Don't let this viewer build its own animation widget.
animation: false,
// Don't let this viewer build its own timeline widget.
timeline: false
});
Here's the Updated Sandcastle Demo.
I'm using Pixi with PixiOverlay on leaflet. I have the following jsfiddle for a dummy simulation. The objective: once you click Add Image 2 - it adds a picture of a hamster randomly on the map.
It (almost) work.
the problem:
Error message: "BaseTexture added to the cache with an id [hamster] that already had an entry"
I couldn't figure our where to put the loader and how to integrate it properly in terms of code organization: (do I need to use it only once?) what if I have other layers to add? So I assume my challenge is here:
this.loader.load((loader, resources) => {...}
Minor: how to reduce the size of the hamster :-)
my JS code (also on jsfiddle)
class Simulation
{
constructor()
{
// center of the map
var center = [1.8650, 51.2094];
// Create the map
this.map = L.map('map').setView(center, 2);
// Set up the OSM layer
L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18
}).addTo(this.map);
this.imagesLayer = new L.layerGroup();
this.imagesLayer.addTo(this.map);
}
_getRandomCoord()
{
var randLat = Math.floor(Math.random() * 90);
randLat *= Math.round(Math.random()) ? 1 : -1;
var randLon = Math.floor(Math.random() * 180);
randLon *= Math.round(Math.random()) ? 1 : -1;
return [randLat,randLon]
}
addImage2()
{
this.loader = new PIXI.Loader()
this.loader.add('hamster', 'https://cdn-icons-png.flaticon.com/512/196/196817.png')
this.loader.load((loader, resources) => {
let markerTexture = resources.hamster.texture
let markerLatLng = this._getRandomCoord()
let marker = new PIXI.Sprite(markerTexture)
marker.anchor.set(0.5, 1)
let pixiContainer = new PIXI.Container()
pixiContainer.addChild(marker)
let firstDraw = true
let prevZoom
let pixiOverlay = L.pixiOverlay(utils => {
let zoom = utils.getMap().getZoom()
let container = utils.getContainer()
let renderer = utils.getRenderer()
let project = utils.latLngToLayerPoint
let scale = utils.getScale()
if (firstDraw) {
let markerCoords = project(markerLatLng)
marker.x = markerCoords.x
marker.y = markerCoords.y
}
if (firstDraw || prevZoom !== zoom) {
marker.scale.set(1 / scale)
}
firstDraw = true
prevZoom = zoom
renderer.render(container)
}, pixiContainer)
this.imagesLayer.addLayer(pixiOverlay);
})
}
addTriangle()
{
console.log("Trinalge")
var polygonLatLngs = [
[51.509, -0.08],
[51.503, -0.06],
[51.51, -15.047],
[21.509, -0.08]
];
var projectedPolygon;
var triangle = new PIXI.Graphics();
var pixiContainer = new PIXI.Container();
pixiContainer.addChild(triangle);
var firstDraw = true;
var prevZoom;
var pixiOverlay = L.pixiOverlay(function(utils) {
var zoom = utils.getMap().getZoom();
var container = utils.getContainer();
var renderer = utils.getRenderer();
var project = utils.latLngToLayerPoint;
var scale = utils.getScale();
if (firstDraw) {
projectedPolygon = polygonLatLngs.map(function(coords) {return project(coords);});
}
if (firstDraw || prevZoom !== zoom) {
triangle.clear();
triangle.lineStyle(3 / scale, 0x3388ff, 1);
triangle.beginFill(0x3388ff, 0.2);
projectedPolygon.forEach(function(coords, index) {
if (index == 0) triangle.moveTo(coords.x, coords.y);
else triangle.lineTo(coords.x, coords.y);
});
triangle.endFill();
}
firstDraw = false;
prevZoom = zoom;
renderer.render(container);
}.bind(this), pixiContainer);
this.imagesLayer.addLayer(pixiOverlay)
}
removeLayer()
{
this.imagesLayer.clearLayers();
}
}
var simulation = new Simulation();
TLDR: Updated jsfiddle:
https://jsfiddle.net/gbsdfm97/
more info below:
First problem: loading resources (textures)
There was error in console because you loaded hamster image on each click:
addImage2()
{
this.loader = new PIXI.Loader()
this.loader.add('hamster', 'https://cdn-icons-png.flaticon.com/512/196/196817.png')
this.loader.load((loader, resources) => {
...
Better approach is to load image (resource) once at beginning and then just reuse what is loaded in memory:
constructor()
{
...
this.markerTexture = null;
this._loadPixiResources();
}
...
_loadPixiResources()
{
this.loader = new PIXI.Loader()
this.loader.add('hamster', 'https://cdn-icons-png.flaticon.com/512/196/196817.png')
this.loader.load((loader, resources) => {
this.markerTexture = resources.hamster.texture;
})
}
...
addImage2()
{
...
let marker = new PIXI.Sprite(this.markerTexture);
Second problem: size of hamsters :)
Scale was set like this:
marker.scale.set(1 / scale)
Which was too big - so changed it to:
// affects size of hamsters:
this.scaleFactor = 0.05;
...
marker.scale.set(this.scaleFactor / scale);
Scale of hamsters (not triangles!) is now updated when zoom changes - so when user uses mouse scroll wheel etc.
Third problem: too many layers in pixiOverlay
Previously on each click on Add Image 2 or Add Triangle button there was added new pixiContainer and new pixiOverlay which was added as new layer: this.imagesLayer.addLayer(pixiOverlay);
New version is a bit simplified: there is only one pixiContainer and one pixiOverlay created at beginning:
constructor()
{
...
// Create one Pixi container for pixiOverlay in which we will keep hamsters and triangles:
this.pixiContainer = new PIXI.Container();
let prevZoom;
// Create one pixiOverlay:
this.pixiOverlay = L.pixiOverlay((utils, data) => {
...
}, this.pixiContainer)
this.imagesLayer.addLayer(this.pixiOverlay);
}
this.pixiOverlay is added as one layer
then in rest of program we reuse this.pixiOverlay
also we reuse this.pixiContainer because it is returned from utils - see:
let container = utils.getContainer() // <-- this is our "this.pixiContainer"
...
container.addChild(marker)
renderer.render(container)
Bonus: Triangles
Now you can add many triangles - one per each click.
Note: triangles do not change scale - this is a difference compared to hamsters.
I need to display different polylines from A to B. So, these lines should be distinguishable from each other. I haved tried to set polylines using pushpoint function with altitude parameter. However it is still on the ground level. And the last polyline I inserted overwrites the previous one.
Altitude value works on markers but I want to apply it on polyline.
I changed the sample code here markers with altitude as below. You can see the orange line is just on top of the gray line when you change the code with the below one. I would like both lines to be displayed like the markers you see above them.
/**
* Calculate the bicycle route.
* #param {H.service.Platform} platform A stub class to access HERE services
*/
function calculateRouteFromAtoB (platform) {
var router = platform.getRoutingService(),
routeRequestParams = {
mode: 'fastest;bicycle',
representation: 'display',
routeattributes : 'shape',
waypoint0: '-16.1647142,-67.7229166',
waypoint1: '-16.3705847,-68.0452683',
// explicitly request altitude values
returnElevation: true
};
router.calculateRoute(
routeRequestParams,
onSuccess,
onError
);
}
/**
* Process the routing response and visualise the descent with the help of the
* H.map.Marker
*/
function onSuccess(result) {
var lineString = new H.geo.LineString(),
lineString2 = new H.geo.LineString(),
routeShape = result.response.route[0].shape,
group = new H.map.Group(),
dict = {},
polyline,
polyline2;
routeShape.forEach(function(point) {
var parts = point.split(',');
var pp= new H.geo.Point(parts[0],parts[1],4000,"SL");
console.log(parts[2]);
lineString.pushLatLngAlt(parts[0], parts[1]);
lineString2.pushPoint(pp);
// normalize the altitude values for the color range
var p = (parts[2] - 1000) / (4700 - 1000);
var r = Math.round(255 * p);
var b = Math.round(255 - 255 * p);
// create or re-use icon
var icon;
if (dict[r + '_' + b]) {
icon = dict[r + '_' + b];
} else {
var canvas = document.createElement('canvas');
canvas.width = 4;
canvas.height = 4;
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(' + r + ', 0, ' + b + ')';
ctx.fillRect(0, 0, 4, 4);
icon = new H.map.Icon(canvas);
// cache the icon for the future reuse
dict[r + '_' + b] = icon;
}
// the marker is placed at the provided altitude
var marker = new H.map.Marker({
lat: parts[0], lng: parts[1], alt: parts[2]
}, {icon: icon});
var marker2 = new H.map.Marker({
lat: parts[0], lng: parts[1], alt: parts[2]-800
}, {icon: icon});
group.addObject(marker);
group.addObject(marker2);
});
polyline = new H.map.Polyline(lineString, {
style: {
lineWidth: 6,
strokeColor: '#555555'
}
});
polyline2 = new H.map.Polyline(lineString2, {
style: {
lineWidth: 3,
strokeColor: '#FF5733'
}
});
// Add the polyline to the map
map.addObject(polyline);
map.addObject(polyline2);
// Add markers to the map
map.addObject(group);
// Zoom to its bounding rectangle
map.getViewModel().setLookAtData({
bounds: polyline.getBoundingBox(),
tilt: 60
});
}
/**
* This function will be called if a communication error occurs during the JSON-P request
* #param {Object} error The error message received.
*/
function onError(error) {
alert('Can\'t reach the remote server');
}
/**
* Boilerplate map initialization code starts below:
*/
// set up containers for the map + panel
var mapContainer = document.getElementById('map'),
routeInstructionsContainer = document.getElementById('panel');
//Step 1: initialize communication with the platform
// In your own code, replace variable window.apikey with your own apikey
var platform = new H.service.Platform({
apikey: window.apikey
});
var defaultLayers = platform.createDefaultLayers();
//Step 2: initialize a map - this map is centered over Berlin
var map = new H.Map(mapContainer,
defaultLayers.vector.normal.map,{
center: {lat:52.5160, lng:13.3779},
zoom: 13,
pixelRatio: window.devicePixelRatio || 1
});
// add a resize listener to make sure that the map occupies the whole container
window.addEventListener('resize', () => map.getViewPort().resize());
//Step 3: make the map interactive
// MapEvents enables the event system
// Behavior implements default interactions for pan/zoom (also on mobile touch environments)
var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
// Create the default UI components
var ui = H.ui.UI.createDefault(map, defaultLayers);
// Now use the map as required...
calculateRouteFromAtoB (platform);
Unfortunately, for now only markers support altitudes.
Polylines should follow in near future.
I have this very odd problem, where the selection box shifts further and further down the closer the marker is to the bottom of the map.
If the marker is at the very top of the map the selection box is right on top of the marker, but if it's further down the map I have to click further and further under the marker to select it.
Has anyone else experienced this? I'm using Openlayers 4.1.1. Here are some code chunks that could be relevant (I'm more than happy to share more code if that can help!):
var imageStyleFunction = function (feature, resolution) {
if (feature.iconSrc) {
var anchorX = feature.anchorX ? feature.anchorX : 0.5;
var anchorY = feature.anchorY ? feature.anchorY : 0.5;
return [new ol.style.Style({
image: new ol.style.Icon({
src: feature.iconSrc,
anchor: [anchorX, anchorY]
})
})];
}
};
...
markerLayer = new ol.layer.Vector({
source: new ol.source.Vector(),
style: imageStyleFunction
});
...
selectionInteraction = new ol.interaction.Select({
condition: ol.events.condition.click,
//hitTolerance: 20,
style: imageStyleFunction
});
selectionInteraction.on('select', function (e) {
var userLocationFeature = _.find(e.target.getFeatures().getArray(),
function (feature) {
return feature.markerType === "userLocation";
});
if (userLocationFeature) {
store.commit("setSelectedUserLocation", userLocationFeature);
} else {
store.commit("setSelectedUserLocation", null);
selectionInteraction.getFeatures().clear();
}
});
...
function createMarker(location, iconFile, markerType, markerId) {
var markerLayerSource = markerLayer.getSource();
var markerFeature = new ol.Feature({
geometry: new ol.geom.Point(transformLonLat(location.longitude, location.latitude))
});
markerFeature.iconSrc = app.config.map.iconPath + iconFile;
if (markerType) {
markerFeature.markerType = markerType;
}
if (markerId) {
markerFeature.markerId = markerId;
}
markerLayerSource.addFeature(markerFeature);
}
Mick's own solution by calling map.updateSize() solved it for me also. I haven't investigated it more closely but I'm assuming it has to do with sizes changing during init of my ionic app. It's a quick fix to a more difficult problem at least.
I am having a few issues with the Google Maps API. I managed to make a cutom map using an image tile. which seems to be working OK. I am just wondering if there is a way to overlay roads on the map?
At the moment I am assuming there are 2 ways this is possible, either some built in function in the API, which I am having troubles finding. I have found paths etc, But I would like roads/streets to have labels, resize on zoom etc, as well as be able to toggle.
The other option was to do a second image tile and overlay that image, which I am not sure how todo at this moment.
If anyone has any info on this, or could point me in the right direction. It would be much appreciated.
/* <![CDATA[ */
/* Global variable that will contain the Google Maps object. */
var map = null
// Google Maps Demo object
var Demo = Demo || {};
// The path to your tile images.
Demo.ImagesBaseUrl = './mapImage/mapTiles/';
Demo.ImagesRoadsUrl = './mapImage/overlayRoads/';
// NuviaMap class
Demo.NuviaMap = function(container) {
// Create map
// This sets the default info for your map when it is initially loaded.
map = new google.maps.Map(container, {
zoom: 1,
center: new google.maps.LatLng(0, 0),
mapTypeControl: false,
streetViewControl: false,
zoomControl: true,
zoomControlOptions: {
style: google.maps.ZoomControlStyle.SMALL
}
});
// Set custom tiles
map.mapTypes.set('nuvia', new Demo.ImgMapType('nuvia', '#4E4E4E'));
map.setMapTypeId('nuvia');
// Loop through the marker info array and load them onto the map.
for (var key in markers) {
var markerData = markers[key];
var marker = new google.maps.Marker({
position: new google.maps.LatLng(markerData.lat, markerData.lng),
map: map,
title: markerData.title,
flat: markerData.flat,
visible: true,
infoBubble: new InfoBubble({
maxWidth: 300,
content: (markerData.image ? '<img src="' + markerData.image + '" width="80" align="left">' : '') + '<h3>' + markerData.title + '</h3>' + (markerData.info ? '<p>' + markerData.info + '</p>' : ''),
shadowStyle: 1,
padding: '10px'
}),
// You can use custom icons, default icons, etc. In my case since a city was a marker the circle icon works pretty well.
// I adjust the scale / size of the icon depending on what kind of city it is on my map.
icon: {
url: markerData.icon,
}
});
// We need to trap the click event for the marker so we can pop up an info bubble.
google.maps.event.addListener(marker, 'click', function() {
this.infoBubble.open(map, this);
});
activeMarkers.push(marker);
}
// This is dumb. We only want the markers to display at certain zoom levels so this handled that.
// Google should have a way to specify zoom levels for markers. Maybe they do but I could not find them.
google.maps.event.addListener(map, 'zoom_changed', function() {
var currentZoom = map.getZoom();
for (var i = 0; i < activeMarkers.length; i++) {
var thisTitle = activeMarkers[i].title;
if (markers[thisTitle]['zoom'][currentZoom])
activeMarkers[i].setVisible(true);
else
activeMarkers[i].setVisible(false);
}
});
// This handles the displaying of lat/lng info in the lat/lng info container defined above in the HTML.
google.maps.event.addListener(map, 'click', function(event) {
$('#latLng').html("Latitude: " + event.latLng.lat() + " " + ", longitude: " + event.latLng.lng());
});
// Listener to display the X/Y coordinates
google.maps.event.addListener(map, 'mousemove', function (event) {
displayCoord(event.latLng);
});
};
// ImgMapType class
Demo.ImgMapType = function(theme, backgroundColor) {
this.name = this._theme = theme;
this._backgroundColor = backgroundColor;
};
// Let Google know what size our tiles are and what our min/max zoom levels should be.
Demo.ImgMapType.prototype.tileSize = new google.maps.Size(256, 256);
Demo.ImgMapType.prototype.minZoom = 1;
Demo.ImgMapType.prototype.maxZoom = 5;
// Load the proper tile.
Demo.ImgMapType.prototype.getTile = function(coord, zoom, ownerDocument) {
var tilesCount = Math.pow(2, zoom);
if (coord.x >= tilesCount || coord.x < 0 || coord.y >= tilesCount || coord.y < 0) {
var div = ownerDocument.createElement('div');
div.style.width = this.tileSize.width + 'px';
div.style.height = this.tileSize.height + 'px';
div.style.backgroundColor = this._backgroundColor;
return div;
}
var img = ownerDocument.createElement('IMG');
img.width = this.tileSize.width;
img.height = this.tileSize.height;
// This tells what tile image to load based on zoom and coord info.
img.src = Demo.Utils.GetImageUrl('tile_' + zoom + '_' + coord.x + '-' + coord.y + '.png');
return img;
};
// Just basically returns the image using the path set way above and the name of the actual image file.
Demo.Utils = Demo.Utils || {};
Demo.Utils.GetImageUrl = function(image) {
return Demo.ImagesBaseUrl + image;
};
// Opacity.
Demo.Utils.SetOpacity = function(obj, opacity /* 0 to 100 */ ) {
obj.style.opacity = opacity / 100;
obj.style.filter = 'alpha(opacity=' + opacity + ')';
};
// Create ye ol' map.
google.maps.event.addDomListener(window, 'load', function() {
var nuviaMap = new Demo.NuviaMap(document.getElementById('nuvia-map'));
});
console.log('Ready Builder');
/* ]]> */
This is the JS I am working off so far, (Credits to http://www.cartographersguild.com/showthread.php?t=21088)