FusionTablesLayer - Get Location column on click - google-maps

I have a Google Maps with a FusionTablesLayer showing railways in Germany (the fusion table can be found here). The geometry column contains the LineString coordinates of each feature.
var railwaysLayer = new google.maps.FusionTablesLayer({
map: map,
query: {
select: 'geometry',
from: '1cVaJLbbxgBErP8PzYkHP-lpWMN8wUmINbdAJS1Y'
}
});
When a user clicks on a railway, i need to know which row (or feature actually) got clicked. This can be done in the click event handler like this:
google.maps.event.addListener(existingRailwaysLayer, 'click', function(e) {
console.log(e.row.geometry.value);
});
This works as expected, but here's the problem: This table got uploaded one month ago from a KML file as a test. I've imported the exact same KML file on a different account today (here), and the geometry column isn't being returned anymore in the event handler.
I checked all the settings and found out that in both tables, the geometry columns are of type Location. However: in the first table, the rows are NOT geocoded. File > Geocode > Begin geocoding effectively starts the process of geocoding each row, while in the second table it says: 'Column is fully geocoded'.
Since everything else is exactly the same, this could very well be the problem. I don't know how to 'un-geocode' the rows in the second table. Since the first one got uploaded a month ago, my guess is that Google now automatically geocodes the rows.
Is there any way to get the geometry column of the feature that got clicked?
Note: It's definitely possible, even when the geometrycolumn is geocoded. In the 'Map of geometry' tab of the second table, you can select the columns that are shown in the infoWindow when a feature is clicked. See here for a working demo.

rows with KML-data will not be included in rows (I'm not sure if this always has been).
I guess the KML-features have been ommitted to save bandwidth
What you can do:
Parse e.infoWindowHtml (as you have noticed it's possible to print the geometry-feature in the InfoWindow, e.infoWindowHtml contains the string used as content for the InfoWindow).
But the automatic InfoWindow-template only shows the coordinates, use a dynamic template, e.g.
{template .contents}
<div class='googft-info-window'>
<b>description:</b> {$data.formatted.description}<br>
<b>name:</b> {$data.formatted.name}<br>
<div style="width:0;height:0;overflow:hidden;">[[{$data.value.geometry}]]</div>
</div>
{/template}
(You may use this template with your table, I've created it, it has the templateId:6)
As you can see the geometry is embedded in a div with no size, yit will not be shown inside the InfoWindow.
The geometry is enclosed by square brackets, use RegExp to find it:
google.maps.event.addListener(railwaysLayer, 'click', function(e) {
var str = e.infoWindowHtml, geo;
if(geo=str.match(/\[\[([^\]]+)]]/)){
geo=geo[1].replace(/(>|<)/g,function(x){
return((x==='>')?'>':'<');
});
console.log(geo);
}
});
Demo:(using your table with my template) http://jsfiddle.net/doktormolle/9YdKW/

Related

Hover on <p> element to highlight geographic boundary, D3 Choropleth

I made a choropleth map of Chicago's 77 neighborhoods in D3.
The only challenge is, it's hard to know which neighborhood is which.
So, what I did was make divs with p elements (containing the neighborhood names) in the body of my HTML file and positioned them into a blank spot in my svg/canvas.
See a visual here.
What I'm trying to do is make it so when you hover over the name, the geographic boundary of the neighborhood highlights. Somehow I need to relate the geography to the text, but I have no idea how.
For a more robust solution, you would ideally want to add an id field to your data. This assumes that your data is some format (such as JSON). You may already have a unique identifier that you can use instead, but if not the following should work.
var i = 0;
for (var x in dataSet)
dataSet[x].id = ++i; //Or i++ for zero-based indexing
Now it depends on how you are generating the svg elements, but ideally you are using the enter function of d3. In that case, just create the result of enter as a variable and use it to append both the path (map portion) as well as the text.
var dataSelect = svg.selectAll(".item").data(data.items);
var dataEnter = dataSelect.enter();
dataEnter.append("path").....
dataEnter.append("text").....text(function(d){return d.label;}) //Using text because this is drawn inside the SVG.
With using the data and enter functions, the created objects automatically have the id data bound to them.
This makes it a simple case of text.id == path.id in your mouseover function.
svg.selectAll(".itemText").on("mouseover", function(textItem){
svg.selectAll(".item").each(function (cityItem){
if (cityItem.id == textItem.id)
d3.select(this).style("fill", "green");
else
d3.select(this).style("fill", function(d){return d.color;});
})
});
I've done this in a fiddle which you can see here
Note that this does not use p elements because ideally, if you're using SVG then you probably should use text elements. If you have to use p elements, then you can still use this general technique, but instead using p.text() as the matching factor on mouseover instead of id, assuming that the name is bound to your path data somewhere.

Best and most performant implementation of dynamic shapes in cesium

I am currently working an application that is using a Cesium Viewer. I need to be able to display a collection of shapes that will be updated dynamically. I am having trouble understanding the best way to do this.
I currently am using Entities and using CallbackProperties to allow for the updating of shapes.
You can through this into a sandcastle to get an idea of how I am doing this. There is a polygon object that is being used as the basis for the cesiumCallback, and it is getting edited by another piece of code. (simulated with the setTimeout)
var viewer = new Cesium.Viewer('cesiumContainer', {});
var polygon = {};
polygon.coordinates = [
{longitude: 0, latitude: 0, altitude: 0},
{longitude: 10, latitude: 10, altitude: 0},
{longitude: 10, latitude: 0, altitude: 0}
];
// converts generic style options to cesium one (aka color -> material)
var polOpts = {};
// function for getting location
polOpts.hierarchy = new Cesium.CallbackProperty(function() {
var hierarchy = [];
for (var i = 0; i < polygon.coordinates.length; i++) {
var coordinate = polygon.coordinates[i];
hierarchy.push(Cesium.Cartesian3.fromDegrees(coordinate.longitude, coordinate.latitude, coordinate.altitude));
}
return hierarchy;
}, false);
viewer.entities.add({polygon: polOpts});
setInterval(function(polygon){
polygon.coordinates[0].longitude--;
}.bind(this, polygon), 1000);
The polygon being passed in is a class that generically describes a polygon, so it has an array of coordinates and style options, as well as a render method that calls this method renderPolygon passing in itself.
This method of rendering shapes works for everything I need it to, but it is not very performant. There are two cases for shapes updating, one type of shape will be updated over a long period of time, as a slow rate like once every few seconds. The other is shapes that will will get updated many times, like thousands, in a few seconds, then not change again for a long time, if ever.
I had two ideas for how to fix this.
Idea 1:
Have two methods, a renderDynamicPolygon and a renderStaticPolygon.
The renderDynamicPolygon method would do the above functionality, using the cesiumCallbackProperties. This would be used for shapes that are getting updated many times during the short time they are being updated.
The renderStaticPolygon method would replace the entities properties that are using callbackProperties with constant values, once the updating is done.
This creates a lot of other work to make sure shapes are in the right state, and doesn't help the shapes that are being updated slowly over a long period of time.
Idea 2:
Similarly to how the primitives work, I tried removing the old entity and adding it again with its updated properties each time its need to be updated, but this resulted in flickering, and unlike primitives, i could not find a async property for entities.
I also tried using primitives. It worked great for polylines, I would simply remove the old one and add a new one with the updated properties. I was also using the async = false to ensure there was no flickering. This issue I ran into here was not all shapes can be created using primitives. (Is this true?)
The other thing I tried was using the geometry instance using the geometry and appearance. After going through the tutorial on the cesium website I was able to render a few shapes, and could update the appearance, but found it close to impossible to figure out how to update the shapes correctly, and also have a very hard time getting them to look correct. Shapes need to have the right shape, a fill color and opacity and a stroke color, opacity and weight. I tried to use the polygonOutlineGeometry, but had not luck.
What would be the best way to implement this? Are one of these options headed the right way or is there some other method of doing this I have not uncovered yet?
[Edit] I added an answer of where I have gotten, but still not complete and looking for answers.
I have came up with a pretty good solution to this, but it still has one small issue.
I made too ways of showing entities. I am calling one render and one paint. Render uses the the Cesium.CallbackProperty with the isConstant property true, and paint with the isConstantProperty false.
Then I created a function to change the an entity from render to paint and vice vera. It goes through the entities callback properties an uses the setCallback property to overwrite the property with a the correct function and isConstant value.
Example:
I create a ellipse based on a circle object I have defined.
// isConst is True if it is being "painted" and false if it is being "rendered"
ellipse: lenz.util.extend(this._getStyleOptions(circle), {
semiMinorAxis: new Cesium.CallbackProperty(
this._getRadius.bind(this, circle),
isConst
),
semiMajorAxis: new Cesium.CallbackProperty(
this._getRadius.bind(this, circle),
isConst
),
})
So when the shape is being updated (while the user is drawing a shape) the shape is rendered with the isConstant being false.
Then when the drawing is complete it is converted to the painted version using some code like this:
existingEntity.ellipse.semiMinorAxis.setCallback(
this._getRadius.bind(this, circle),
isConst
);
existingEntity.ellipse.semiMajorAxis.setCallback(
this._getRadius.bind(this, circle, 1),
isConst
);
This works great performance wise. I am able to draw hundreds of shapes without the frame dropping much at all. I have attached a screen shot of the cesium map with 612 entities before and after my changes, the frame rate is in the upper right using the chrome render tool.
Before: Locked up at fps 0.9
Note: I redacted the rest of the ui, witch makes the globe look cut off, sorry
And after the changes: The fps remains at 59.9, almost perfect!
Whenever the entity is 'converted' from using constant to not constant callback properties, it and all other entities of the same type flash off then on again. I cannot find a better way to do this conversion. I feel as thought there must still be some thing I am missing.
You could try using a PositionPropertyArray as the polygon's hierarchy with SampledPositionProperty for any dynamic positions and ConstantPositionProperty for any static positions. I'm not sure if it would perform any better than your solution, but it might be worth testing. Here is an example of how it might work that you can paste into the Cesium Sandcastle:
var viewer = new Cesium.Viewer('cesiumContainer', {});
// required if you want no interpolation of position between times
var noInterpolation = {
type: 'No Interpolation',
getRequiredDataPoints: function (degree) {
return 2;
},
interpolateOrderZero: function (x, xTable, yTable, yStride, result) {
if (!Cesium.defined(result)) {
result = new Array(yStride);
}
for (var i = 0; i < yStride; i++) {
result[i] = yTable[i];
}
return result;
}
};
var start = viewer.clock.currentTime;
// set up the sampled position property
var sampledPositionProperty = new Cesium.SampledPositionProperty();
sampledPositionProperty.forwardExtrapolationType = Cesium.ExtrapolationType.HOLD;
sampledPositionProperty.addSample(start, new Cesium.Cartesian3.fromDegrees(0, 0)); // initial position
sampledPositionProperty.setInterpolationOptions({
interpolationAlgorithm: noInterpolation
});
// set up the sampled position property array
var positions = [
sampledPositionProperty,
new Cesium.ConstantPositionProperty(new Cesium.Cartesian3.fromDegrees(10, 10)),
new Cesium.ConstantPositionProperty(new Cesium.Cartesian3.fromDegrees(10, 0))
];
// add the polygon to Cesium viewer
var polygonEntity = new Cesium.Entity({
polygon: {
hierarchy: new Cesium.PositionPropertyArray(positions)
}
});
viewer.zoomTo(viewer.entities.add(polygonEntity));
// add a sample every second
var counter = 1;
setInterval(function(positionArray) {
var time = new Cesium.JulianDate.addSeconds(start, counter, new Cesium.JulianDate());
var position = new Cesium.Cartesian3.fromDegrees(-counter, 0);
positionArray[0].addSample(time, position);
counter++;
}.bind(this, positions), 1000);
One nice thing about this is you can set the timeline start/end time to a reasonable range and use it to see your polygon at any time within the sample range so you can see the history of your polygons through time (See here for how to change the timeline start/end time). Additionally, you don't need to use timers to set the positions, the time is built in to the SampledPositionProperty (although you can still add samples asynchronously).
However, this also means that the position depends on the current time in the timeline instead of a real-time array value. And you might need to keep track of a time somewhere if you aren't adding all the samples at once.
I've also never done this using ellipses before, but the semiMinorAxis and semiMajorAxis are properties, so you might still be able to use a SampledProperty.
Of course, this doesn't really matter if there are still performance issues. Hopefully it will improve as you don't need to recreate the array from scratch each callback and, depending on how you're getting the data to update the polygons, you might be able to add multiple samples at once. This is just speculation, but it's something to consider.
EDIT
Cesium can handle quite a bit of samples added to a sampled position, for example in the above code if you add a million samples to the position it takes a few seconds to load them all, but renders the polygon at any time without any performance issues. To test this, instead of adding samples using a timer, just add them all directly to the property.
for (var i = 0; i < 1000000; i++) {
var time = new Cesium.JulianDate.addSeconds(start, i, new Cesium.JulianDate());
var position = new Cesium.Cartesian3.fromDegrees(-(i % 2), 0);
positions[0].addSample(time, position);
}
However, if you run into memory problems currently there is no way to remove samples from a position property without accessing private variables. A work around would be to periodically create a new array containing new position properties and use the previous position property array's setValue() method to clear previous values or perhaps to use a TimeIntervalCollectionProperty as in this answer and remove time intervals with the removeInterval method.

Why it does not showing the year value .in D3plus scatter?

I am using D3plus for data visualization . but in the x axis wrong data is showing instead of what i wrote in .x("year") to show .
http://jsfiddle.net/MituVinci/a77kz0dr/
enter code here
var visualization = d3plus.viz()
.container("#viz") // container DIV to hold the visualization
.data(sample_data) // data to use with the visualization
.type("scatter") // visualization type
.id("Reason") // key for which our data is unique on
.x("year") // key for x-axis
.y("Female") // key for y-axis
.draw()
I also want to resize the width and height of this and also want to show it using an external json file how can i do it ?
Since you have "Reason" as your .id() variable, D3plus is aggregating all data points that have the same "Reason". So the "x" position for "Family Feud" is 2010+2011+2012+2013+2014 or 10060, which is where all of your bubbles are located.
If you want to display each bubble individually, you could create a separate variable called "ReasonYear", concat the text of the Reason and Year fields together and then use .id("ReasonYear") for your visualization.
Use .width() and .height() to control the width and height respectively of your visualization.
Use .data() to load data from an external JSON file
Documentation can be found here: https://github.com/alexandersimoes/d3plus/wiki/Visualizations

D3.js: How to combine 2 datasets in order to create a map and show values on.mouseover?

I would like to combine two datasets on a map in D3.js.
For example:
1st dataset: spatial data in .json.
2nd dataset: Data to the areas in .csv
--> When you hover on the map a tooltip should show a sentences with some data from the 2nd dataset.
I am able to make the map and show a tooltip with data within the .json-file, but how do I insert the 2nd dataset?
A new function within my function that creates the map?
Do I have to take a completely new way?
Should I merge the .json-file with my 2nd dataset before using d3.js?
I appreciate any thoughts! :)
So, I think what you're asking is how to take spatial data from json and join it with some csv data that is loaded separately?
I did something similar with a choropleth map I was drawing and basically I just created a map of topology element ids to data objects and then I did a lookup using the topology element id to get whatever I wanted to associate with the actual drawn map element (I was using this method to set the color for the choropleth based on the fips country code).
So basically, draw the map so that you have an id associated with each map element that you want to be able to hover over. Then, in your mouseover/mouseout handlers, you will use that id to lookup the data you want to show in the tooltip and either use the svg title element or tipsy or manually draw an svg text element or whatever to show the tooltip.
Here's a couple useful references for drawing tooltips:
https://gist.github.com/biovisualize/1016860
http://jsfiddle.net/reblace/6FkBd/2/
From the fiddle:
function mouseover(d) {
d3.select(this).append("text")
.attr("class", "hover")
.attr('transform', function(d){
return 'translate(5, -10)';
})
.text(d.name + ": " + d.id);
}
// Toggle children on click.
function mouseout(d) {
d3.select(this).select("text.hover").remove();
}
Basically, it's appending an SVG text element and offsetting it from the position of the element being hovered over.
And here's a sample of how I look up data in an external map:
// Update the bound data
data.svg.selectAll("g.map path").transition().duration(750)
.style("fill", function(d) {
// Get the feature data from the mapData using the feature code
var val = mapData[d.properties.code];
// Return the colorScale value for the state's value
return (val !== undefined)? data.settings.colorScale(val) : undefined;
});
If your data is static, you can join it into your topojson file (if that's what you're using). https://github.com/mbostock/topojson/wiki/Command-Line-Reference
The client could change my data, so I kept it separate and redrew the map each time the data changed so that the colors would update. Since my data was topojson, I could access the feature id from the map data using d.properties.code (because I had joined the codes into the topojson file using the topojson tool I reference above... but you could use whatever unique id is in the spatial data file you have).

How to display two or more markers on the same location?

I have a question about the GOOGLE MAP API. If you have more than two data that share similar address, how do you show the Pins Drop on the same address?
Example
You have data such as the following:
Name=>Ray | Address=>Melon Park, California, USA ;
Name=>John | Address=>Melon Park, California, USA ;
Name=>Steve | Address=>Melon Park, California, USA
You want to display 3 Pins Drop for the similar address on Google Map.
The post linked above has some good advice. Your options are really:
Offset the markers slightly, so instead of displaying them all on the same they are all on really close to each other. Just add add or subtract small delta to each of them.
Use different icons for each location. If you know the maximum number of markers that could overlap (like 4?) at the same location, you could make your own rotated icons, so instead of pointing "down" in the typical teardrop shape they could point left, right, or up.
Handle the data overlap in your infowindow / UI.
Adding small delta will work like this (this script is written in PHP):
$random_num_lat = .00065 * mt_rand(1, 10); // delta to the lat value added
$random_num_lng = .00065 * mt_rand(1, 10); // delta to the lon value added
$lat=$row['lat']+$random_num_lat;
$lon=$row['lon']+$random_num_lng;
//now in loop it will be three `enter code here`marker display seperate to each other
while ($row = mysql_fetch_array($query)) {
echo ("addMarker($lat, $lon,'<b>$name</b><br/>$desc1');\n");
}
I achieved this using a "spiderfier" to "spiderfy".
There is a GoogleMaps v3 Spidifier available here (demo).
Leaflet.js can also load GoogleMaps v3 and has a beautiful clustering spidifier available here (demo).
Note: Following is a more of hack than a solution:
Write an algorithm that checks for repeated coordinates in array.
When a duplicate is found, add a small value such as 0.000001 to the found coordinates.
Note that the smaller the value the more you have zoom in to see the difference.