I'm trying to rewrite grid_control.js in MapBox to allow for several tooltips from gridControl to appear together at the same time in the same box.
The gridControl is activated thus:
var gridLayer = L.mapbox.gridLayer('example-map.sdfagg22gd');
var tileLayer = L.mapbox.tileLayer('example-map.sdfagg22gd')
map.addLayer(tileLayer);
map.addLayer(gridLayer);
map.addControl(L.mapbox.gridControl(gridLayer));
Instead of rewriting the source code, however, I'd like to extend the GridControl class, and make the code more compatible with future mapbox.js versions.
var GridControl = L.Control.extend({
options: {
pinnable: true,
follow: false,
sanitizer: require('sanitize-caja'),
touchTeaser: true,
location: true
},
How should I do that, and is it advisable?
Also, any ideas on how to allow for several tooltips at the same time? Will I need a counter for active tooltips and redo all actions based on that? Any pointers would be greatly appreciated.
From what I understand, the possibility for concurrent tooltips is possible, as two separate boxes with tooltip content will appear with {follow : false} option. But how to get this into a mouse-centric moving tooltip (which is activated by {follow: true})?
Simply:
// get gridControl object and pass it for modification
var gridControl = L.mapbox.gridControl(gridLayer, {follow:true});
// modify the prototype
gridControl.__proto__._onPopupClose = function() {
this._currentContent = null;
this._lastContent = null;
this._pinned = false;
};
Related
so i have a set of markers each stored in different Layers... lets say:
let earth= {
layer:{
...
Markers:{...}
}
}
let mars= {
layer:{
...
Markers:{...}
}
}
let pluto= {
layer:{
...
Markers:{...}
}
}
now how i switch to different maps (with individual markers) is with
function setMap(selectedMap, htmlElement, ifSub = false) {
if (currentMap != selectedMap) {
map.removeLayer(window[currentMap].Layer)
map.addLayer(window[selectedMap].Layer)
currentMap = selectedMap
}
}
With a button (and on launch) i have given some of the markers on pluto a class (lets say "Visited") which changes css for them.
However when i change the map to Earth and back to Pluto
the markers from Pluto "forget" that they had a class in the first place.
how would i keep the css stored with the marker? and added back to the marker when map.addLayer(...) is called?
The problem here is that when a Marker is removed from the map, its icon image is deleted, so any classes or CSS styling you had added manually to it are lost. A new image is created the next time the Marker is added to the map again.
If you just need to make simple changes to the appearance of marker icons, you can create a set of icons with the different images and classes you need, and set the Marker to use the appropriate one. You can set the appropriate initial icon using myMarker = new L.Marker(latlngs, {icon: myIcon}), then change it later with mymarker.setIcon(myOtherIcon). The Marker will remember which Icon it has been set to use, even when it has been removed from the map and added back again. The Leaflet documentation includes a tutorial on using custom icons that shows how to create your own icons. If you want them to have different classes, use the className option when creating them.
Simply using icons with different classes may not work if you need to be able to add and remove classes independently. However, we can extend the definition of a Marker to add class-setting functionality. The following code creates a new class of L.ClassedMarker objects, based on the source code for L.Marker:
L.ClassedMarker = L.Marker.extend({
options: {
classes: []
},
addClass: function(className) {
if(this.options.classes.indexOf(className) === -1) {
this.options.classes.push(className);
}
this._initIcon();
},
getClasses: function() {
return this.options.classes;
},
removeClass: function(className) {
var index = this.options.classes.indexOf(className);
if(index > -1) {
this.options.classes.splice(index, 1);
}
this._initIcon();
},
_initIcon: function() {
L.Marker.prototype._initIcon.apply(this, null);
for(let cls of this.options.classes) {
L.DomUtil.addClass(this._icon, cls);
}
}
});
L.classedMarker = function(latLng, options) {
return new L.ClassedMarker(latLng, options);
}
We can use a ClassedMarker in the same way as a normal Marker, but when creating it, we can specify an (optional) list of classes to add to the icon:
var myMarker = L.classedMarker(latlngs, {classes: ["new"]}).addTo(map);
You can also add or remove classes after the ClassedMarker has been created:
myMarker.removeClass("new");
myMarker.addClass("visited");
The ClassedMarker will remember which classes you have added, regardless of whether you add or remove it from the map, or change the marker's icon. You can check what classes have been added using myMarker.getClasses(). Note this only returns your custom classes, not the ones used internally by Leaflet, or set using the className option of the marker's icon.
The code works by maintaining a list of classes in the ClassedMarker's options, rather than in the icon. Each time the icon needs to be updated, it calls the _initIcon() code from the original L.Marker class, but then uses L.DomUtil.addClass() to apply the stored class names to the new icon image.
we are using OpenLayers 4.6.4 with the ol-ext extension in order to show beautiful looking clusters. That is working fine so far, but we need to link each cluster with custom informations (like putting a simple field into the cluster object) and once the cluster is clicked on the map, i want to retrieve it with the arguments delivered in the callback.
I was not able to find a simple example on putting custom fields on a cluster and retrieving them once i click them on the map.
The event i add my listener is handled by ol.interaction.SelectCluster from ol-ext
Any ideas?
You cannot change the cluster features so easily, but that's actually not necessary to get information about it.
Cluster is just a layer source which clusters (who would have guessed) an underlaying source. It creates Features, which in turn have the represented Features stored in a property features.
The ol-ext example demonstrates how to read the contained features:
var selectCluster = new ol.interaction.SelectCluster(.....);
selectCluster.getFeatures().on(['add'], function (e)
{ var c = e.element.get('features');
if (c.length==1)
{ var feature = c[0];
$(".infos").html("One feature selected...<br/>(id="+feature.get('id')+")");
}
else
{ $(".infos").text("Cluster ("+c.length+" features)");
}
})
Without any interaction, you could do this:
map.on('singleclick', function(evt) {
const feature = map.forEachFeatureAtPixel(
evt.pixel,
function(someFeature){ return someFeature; }, // stop at the very first feature
);
const containedFeatures = feature.get('features');
});
I'm a learner of JavaScript and have a problem with Mapbox GL JS: I have a style in Mapbox Studio, where there is one my layer — "locations". I've added it as a tileset. There are two GeoJSON-points in this layer, but I can't get them in GL JS.
I've found that I should use method querySourceFeatures(sourceID, [parameters]), but I have problems with correct filling its parameters. I wrote:
var allFeatures = map.querySourceFeatures('_id-of-my-tyleset_', {
'sourceLayer': 'locations'
});
..and it doesn't work.
More interesting, that later in the code I use this layer with method queryRenderedFeatures, and it's okay:
map.on('click', function(e) {
var features = map.queryRenderedFeatures(e.point, {
layers: ['locations']
});
if (!features.length) {
return;
}
var feature = features[0];
flyToPoint(feature);
var popup = new mapboxgl.Popup({
offset: [0, -15]
})
.setLngLat(feature.geometry.coordinates)
.setHTML('<h3>' + feature.properties.name + '</h3>' + '<p>' +
feature.properties.description + '</p>')
.addTo(map);
});
I have read a lot about adding layers on a map and know that the answer is easy, but I can't realise the solution, so help, please :)
Here is the project on GitHub.
Your problem is that your map, like all maps created in Mapbox Studio by default, uses auto-compositing. You don't actually have a source called morganvolter.cj77n1jkq1ale33jw0g9haxc0-2haga, you have a source called composite with many sub layers.
You can find the list of layers like this:
map.getSource('composite').vectorLayerIds
Which reveals you have a vector layer called akkerman. ("locations" is the name of your style layer, not your source layer). Hence your query should be:
map.querySourceFeatures('composite', {
'sourceLayer': 'akkerman'
});
Which returns 4 features.
There are lots of questions about Mapbox get features after filter or Mapbox get features before filter. And I could see there are many posts are scattering around but none of them seem to have a FULL DETAILED solution. I spend some time and put both solution together under a function, try this in jsbin.
Here it is for someone interested:
function buildRenderedFeatures(map) {
// get source from a layer, `mapLayerId` == your layer id in Mapbox Studio
var compositeSource = map.getLayer(mapLayerId.toString()).source;
//console.log(map.getSource(compositeSource).vectorLayers);
var compositeVectorLayerLength = map.getSource(compositeSource).vectorLayers.length - 1;
//console.log(compositeVectorLayerLength);
// sourceId === tileset id which is known as vector layer id
var sourceId = map.getSource(compositeSource).vectorLayers[compositeVectorLayerLength].id;
//console.log(sourceId);
// get all applied filters if any, this will return an array
var appliedFilters = map.getFilter(mapLayerId.toString());
//console.log(appliedFilters);
// if you want to get all features with/without any filters
// remember if no filters applied it will show all features
// so having `filter` does not harm at all
//resultFeatures = map.querySourceFeatures(compositeSource, {sourceLayer: sourceId, filter: appliedFilters});
var resultFeatures = null;
// this fixes issues: queryRenderedFeatures getting previous features
// a timeout helps to get the updated features after filter is applied
// Mapbox documentation doesn't talk about this!
setTimeout(function() {
resultFeatures = map.queryRenderedFeatures({layers: [mapLayerId.toString()]});
//console.log(resultFeatures);
}, 500);
}
Then you call that function like: buildRenderedFeatures(map) passing the map object which you already have when you created the Mapbox map.
You will then have resultFeatures will return an object which can be iterated using for...in. You can test the querySourceFeatures() code which I commented out but left for if anyone needs it.
I'm trying to create custom zoom in/out buttons and I've got it working except for the most important part, the zooming..
I've created a map chart like this:
$scope.chart = new Highcharts.Map(config);
Then I have two functions which run whenever you click on zoom in or out:
highcharts.zoomIn = function() {
$scope.chart.mapZoom(0.5);
};
highcharts.zoomOut = function() {
$scope.chart.mapZoom(2);
};
Setting the mapZoom of the chart doesn't give me an error nor does it do anything. I tried calling $scope.chart.redraw() afterwards but it didn't help either. Also saw that it already calls the redraw function in the source code of the mapZoom function.
I can't find any information on how to do this so what exactly am I doing wrong here?
So, in Sketch, you can mark a layer/group as exportable.
And then the layer/group can be exported as .png/.svg/.pdf etc. I was trying to make a Sketch Plugin recently, where I need to mark a layer/group as exportable from code. A layer in code is represented using MSLayer and group is MSLayerGroup. The sketch documentation is not mature enough yet, so I used ClassDump to extract all the headers that has been used in the app. I have been looking for a method that might seem to do my job, but it has been days and am still out of luck. Can anybody please help me out in this regard?
Sketch supports slice and export to image. You can use - (void)saveArtboardOrSlice:(id)arg1 toFile:(id)arg2;
method of MSDocument.
This is almost how to do it.
var loopLayerChildren = [[layerToExport children] objectEnumerator],
rect = [MSSliceTrimming trimmedRectForSlice:layer],
useSliceLayer = false,
exportFilePath,
slice;
// Check for MSSliceLayer and overwrite the rect if present
while (layerChild = [loopLayerChildren nextObject]) {
if ([layerChild class] == 'MSSliceLayer') {
rect = [MSSliceTrimming trimmedRectForSlice:layerChild];
useSliceLayer = true;
}
}
slice = [MSExportRequest requestWithRect:rect scale:1];
if (!useSliceLayer) {
slice.shouldTrim = true;
}
// export to image file
[(this.document) saveArtboardOrSlice: slice toFile:exportFilePath];
Reference from #GeertWill's sketch-to-xcode-assets-catalog plugin.