TileMill Interactive layers not working when added independently with MapBox.js - javascript

I have a student who is using MapBox.js (v1.6.0) to display some tiles that they made in TileMill. These tiles use the Tooltip functionality provided by TileMill (documentation) to add some interactivity. My student is also using a MapBox Streets layer to give some detailed views of roadways, etc. The problem is, when I use both of these layers together in the map, the interactivity from the tiles doesn't work.
Here is the code that doesn't work:
var map = L.mapbox.map("map");
var tilesOSM = L.mapbox.tileLayer("username.id1");
var tilesTileMill = L.mapbox.tileLayer("username.id2");
map
.addLayer(tilesOSM)
.addLayer(tilesTileMill)
.setView(L.latLng(61, -160.5), 4)
.setMaxBounds(L.latLngBounds(L.latLng(50,-180), L.latLng(72,-129)));
We have tried several iterations of this code, but the only way we can get it to work is by using the L.mapbox.map(3) method and then using the _ insertAtTheBottom_ parameter of the L.map.addLayer() function.
var map = L.mapbox.map("map", "username.id2", {});
map
.addLayer(L.mapbox.tileLayer("username.id1"), true)
.setView(L.latLng(61, -160.5), 4)
.setMaxBounds(L.latLngBounds(L.latLng(50,-180), L.latLng(72,-129)));
My question is three fold.
What is the difference between these two implementations?
Why is the tileLayer created using L.mapbox.tileLayer() different than the one created and automatically added using L.mapbox.map(3)?
Are there plans to address this discontinuity in future changes to the API or will support for interactive tiles be dropped in TileMill 2?

What is the difference between these two implementations?
If you check out what L.mapbox.map does internally, it adds a gridLayer and gridControl for the layer you specify. Basically the map constructor makes all of the safe assumptions it could make and does them automatically as a convenience.
Why is the tileLayer created using L.mapbox.tileLayer() different than the one created and automatically added using L.mapbox.map(3)?
It's the same - there's a gridLayer and gridControl in the mix when you use L.mapbox.map(3), and those are what make things interactive.
Are there plans to address this discontinuity in future changes to the API
It's not as much a discontinuity than an API design: we decided to keep tileLayers decoupled from gridLayers and gridControls so you can mix & match them - if you want to switch which layer interactive features come from, or disable interactivity without disabling the tile layer, you can.
will support for interactive tiles be dropped in TileMill 2?
No, you can use TileMill 2 and see how it supports interactivity. We aren't going to remove or phase this out, though vector tiles will have new methods of interaction.
For your second example, you would want something like:
var map = L.mapbox.map("map", "username.id2", {});
var gridLayer = L.mapbox.gridLayer("username.id1").addTo(map);
var gridControl = L.mapbox.gridControl(gridLayer).addTo(map);
map
.addLayer(L.mapbox.tileLayer("username.id1"), true)
.setView(L.latLng(61, -160.5), 4)
.setMaxBounds(L.latLngBounds(L.latLng(50,-180), L.latLng(72,-129)));

Related

Leaflet layer controls for array properties mess up results, how to fix? better solution for large amounts of geoJSON data and several filters?

My end goal is to show a map with several hundred markers, each containing some information in a popup. Those markers are fairly close and some are in the very same spot, so they should be clustered and must be separately clickable for the popup. I also want to be able to filter them by several properties, starting with two array properties (phases and tags).
I actually managed to set all of that up quite nicely by using the MarkerCluster and MarkerCluster-LayerSupport plugins. I have 19 different tags and 4 different phases with corresponding FeatureGroups, all the markers are added to the respective groups, and there are two controls with checkboxes on the map. You can check it out here if you like.
However, these controls act a little weirdly, or rather, not quite the way I'd like them to. All markers are on at least two layers, so when I uncheck "Tag #2", the FeatureGroup with all the markers with tag #2 is removed. That includes markers with tag #3 and tag #6, which are still checked and which therefore I'd like to keep on the map… so when you check and uncheck boxes in both controls, the results become quite unpredictable.
Is there a way to fix this, by somehow reevaluating all the checked layers? Or should I go a completely different route with the filtering? For example this seems to be a jQuery filter. I find it hard to gauge performance issues though – I'm importing all the data into a separate geoJSON file with PHP, and I expect to get several hundred markers at some point, possibly more. I'm fairly new to Leaflet and handling large amounts of data in JS.
I will of course post my code if you'd like to see it, but since it's all working as intended and it's quite a lot, I'm not sure it's useful at this point.
Any pointers, ideas and best practice tips are most appreciated!
I have worked a little with Leaflet. In my experience, you need to have in mind that you need to group the common markers in a single layer, but the geo-JSON files put all markers in a single layer, no matter with properties has.
I did something similar to you, but I preprocessed all added markers to group them by specific properties. It is good because it can give you more control and do whatever you need, but that needs more initial time to preprocess all the info (and more if you are planning to have thousands of markers).
To check and manage all possible points you can use the pointToLayer method in the geoJSON loading:
const tagCategories = {};
.
.
.
L.geoJSON(someGeojsonFeature, {
pointToLayer: function (feature, latlng) {
const tagName = feature.properties.tagName;
const marker = L.circleMarker(latlng, geojsonMarkerOptions);
if (!tagCategories[tagName]) {
tagCategories[tagName] = [];
}
tagCategories[tagName].push({marker, feature});
return marker;
}
}).addTo(map);
.
.
.
function myFilterFunction({feature}) {
return feature.properties.year === '1981';
}
document.querySelector('.tag1Btn').addEventListener('click', () => {
const places = tagCategories[tagName].filter(myFilterFunction);
// Do whatever you need with places.
})
My best advice is to try to use vanilla js to reduce the overhead of preprocessing because libraries like jQuery, lodash and underscore do the same and in some cases are fast, but generally, Vanilla JS is faster if is used properly.

cesium - moving billboards

I am testing Cesiumjs to see if it can reflect a near-real-time expreience - for example: position of airplanes.
For that, I need to draw billboards and make them move - which I know is possible with cesium, just not sure how.
The code looks like this:
var billboards = scene.primitives.add(new Cesium.BillboardCollection());
var billboard = {
image : '/path/to/logo.png',
position : Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883)
};
billboards.add(billboard);
My question is how do I change the position of the billboard. I couldn't find ant documentation that would explain.
I thought doing:
billboard.position = ... //new position
but how will cesium know that I've changed the position attribute unless it somehow turns that reference into a observable object.
So how do I update the location?
Thanks.
Cesium does indeed listen for changes to billboard.position
(source code here), so it is correct behavior for apps to simply write a new position.
Note that you must write the whole position at once, meaning you may not write to billboard.position.x. Instead, keep a "scratch" Cartesian3 around (don't create a new one every animation frame at 60fps), write to the x,y,z properties of your scratch variable, and then assign your scratch variable to billboard.position. You can see in the source that the assigned value will be cloned into another pre-existing Cartesian3, so you may immediately reuse the scratch variable.
Here's an example:
// Just once at app startup. Don't call "new" at 60fps.
var scratchCartesian3 = new Cesium.Cartesian3();
var ellipsoid = viewer.scene.mapProjection.ellipsoid;
function onTick() {
// This is safe to call at 60fps.
billboard.position = Cesium.Cartesian3.fromDegrees(
lon, lat, alt, ellipsoid, scratchCartesian3);
}
Also note that your question and the above answer are focused on the "Graphics Primitive" layer of the Cesium API. Cesium has one higher layer, called the "Entity" API, that you can use if you want Cesium to handle the concept of user-selectable objects with pop-up descriptions etc. Here's a Sandcastle demo showing how to add a billboard as a property of an entity, instead of as a primitive. This allows you to add other properties to the same entity, for example a name, description, label, 3D model, etc, and have them all be controlled from the same position property, and have Cesium take care of popup descriptions. The position property is more complex for entities than for primitives, for example it can be constant or sampled. This allows entities to change position over time when the timeline is shown.

LatLong falls within a given polygon in D3 + Leaflet

I am trying to learn how to use the Javascript library leaflet along with d3 to create various map visualisations.
I have been following this tutorial which creates a choropleth map of the United States with some interactivity. This provides some of what I need, but the main functionality I want is to have a list of lat/long coordinates classified according to which region they belong to.
This would mean, in the tutorial map for example, if I had a lat long value (55, -3) which fell within the state of Arizona's polygon, the program could classify this point as belonging to Arizona.
Is there a function in the leaflet (or d3) library which will allow me to enter a lat long coordinate as a parameter and return the name of the feature it belongs to? The tutorial above allows you to attach a function to every feature via the onEveryFeature property and can fire mouseover events when each feature is hovered over. Surely there is a way to extend this functionality to numerically entered data instead of mouse points?
Leaflet would need some tweaking if you wish to do this. It leaves the handling of mouseclicks to the browser and therefore does not need logic for determining if a point lies inside a polygon.
I am not very knowledgeable about d3 but it's not glaringly obvious to me how it'd do this out of the box. Looking at the polygon code, I do find a clipping algorithm and intersection of infinite lines.
If you add a third library, however, this should be rather simple.
The OpenLayers Geometry library can determine if a point lies inside a polygon.
EDIT: I got this to work, see also http://jsfiddle.net/VaY3E/4/
var parser = new OpenLayers.Format.GeoJSON();
var vectors = parser.read(statesData);
var lat = 36;
var lon = -96;
var point = new OpenLayers.Geometry.Point(lon, lat);
for( var i = 0; i< vectors.length; i++ ){
if(vectors[i].geometry.intersects(point)){
alert(vectors[i].attributes['name']);
}
}
Or you could use https://github.com/maxogden/geojson-js-utils , a bit more specific library. It looks like it knows how to read GeoJSON and it has a method gju.pointInPolygon. I've not tested it though.

custom google maps icons

Is there any way to create custom dynamic icons for the markers?
What I want to create is a list of points and put a number "n" within markers specifying it's the n-th element.
Yes. You can either use custom icons with Numeric Markers or the built-in numbering system on Google has for markers.
Assuming the last option (not using custom markers), your marker definition code would look like:
var NUMBER_SHOWN='34';
var ICON_COLOR = 'ff6600';
var ICON_TEXT_COLOR = '000000';
var marker = new google.maps.Marker({
position: [LatLong Obj],
map: [Map Obj],
icon: 'http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld='+NUMBER_SHOWN+'|'+ICON_COLOR+'|'+ICON_TEXT_COLOR
});
I believe this only works for two-digit number, though I'm not sure.
EDIT
Geocodezip has correctly pointed out that the second approach has now been deprecated, so it looks like you're stuck using the first approach (using google's custom numbered-icons). Have a look at Daniel Vassallo's answer here on how to use it. If none of the markers fit your needs (colors, look, etc) you can create your own custom markers for each of the numbers (if you know how, you can write a server-side script that you can pass GET vars to and have it build the icon for you using GD etc, but you can also just build all the icons by hand)

create 3d shape in google earth plugin

first of all thanks for reading.
I have a web application that heavily uses Google Earth Plugin to show some sensor data and other stuff.
I'm trying to give the user the capability to define areas and volumes drawing them in the plugin.
I was able to add area features (such as creation, visualization, editing and deletion).
Now i'm working on volumes but i really do not know what is the best way to handle them. An inportant thing to note is that i'm only interested in volumes with parallel upper and lower surface (no pyramid, no complex figures, only prisms)
The first idea that came in my mind is to create a custom object made of 2 polygon and an array of edges to connect every polygon vertex of the upper surface to the respective one in the lower surface.
Something like:
//Create the upper surface (polygon)
var aPolygonUpperPlacemark = ge.createPlacemark("");
var aPolygonUpper = ge.createPolygon("");
aPolygonUpper.setAltitudeMode(ge.ALTITUDE_RELATIVE_TO_GROUND);
aPolygonUpperPlacemark.setGeometry(aPolygonUpper);
var aOuterUpper = ge.createLinearRing("");
aPolygonUpper.setOuterBoundary(aOuterUpper);
ge.getFeatures().appendChild(aPolygonUpperPlacemark);
//Create the lower surface (polygon)
var aPolygonLowerPlacemark = ge.createPlacemark("");
var aPolygonLower = ge.createPolygon("");
aPolygonLower.setAltitudeMode(ge.ALTITUDE_RELATIVE_TO_GROUND);
aPolygonLowerPlacemark.setGeometry(aPolygonLower);
var aOuterLower = ge.createLinearRing("");
aPolygonLower.setOuterBoundary(aOuterLower);
ge.getFeatures().appendChild(aPolygonLowerPlacemark);
var myPrism = {
upperSurface: aPolygonUpperPlacemark,
lowerSurface: aPolygonLowerPlacemark,
edges: new Array()
};
The proble here is that the lateral surfaces will not get displayed as real surfaces but only as lines. On the other hand i could probably create another polygon for each lateral surface but this would make the management of such a 3d shape more complex than what i'd like it to be.
So my question is, is there any better way to handle 3d shapes or maybe a built-in geometry?
Nota that i cannot rely in 3d models (so external Kmz cannot be loaded) as at the end the 3d shape creation will be a user's feature.
Just create the upper polygon (e.g. ensure the coordinates include altitude), and then ensure is set to 1. You can do this in the API using setExtrude(true).
See https://developers.google.com/kml/documentation/kml_tut#polygons for details
I'd also recommend you check out the utility library - it makes things like this much more concise. See for example this extruded polygon example: http://earth-api-utility-library.googlecode.com/svn/trunk/extensions/examples/poly-draw-extruded.html

Categories