Im using the follwing UK Geo JSON to render a UK SVG Map
http://martinjc.github.io/UK-GeoJSON/json/eng/topo_eer.json
On this map i want to be able to take longitude + latitude points and plot onto the map.
I am adding a GeometryCollection place to the map in the following way:
data.objects.places = {
type: "GeometryCollection",
geometries: [
{
type: "Point",
coordinates: [-0.127758, 51.507351], // London
properties: {
name: "London - Testing"
}
}
]
};
However the coordinates are not in the correct place.
Below is the full javascript.
var width = 960;
var height = 1000;
var projection = d3.geo.albers()
.center([0, 55.4])
.rotate([4.4, 0])
.parallels([50, 60])
.scale(4000)
.translate([width / 2, height / 2]);
var path = d3.geo.path().projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("topo_eer.json", function(error, data) {
// Create path for the UK
svg.selectAll(".subunit")
.data(topojson.feature(data, data.objects.eer).features)
.enter().append("path")
.attr("class", function(d) { return "subunit " + d.id; })
.attr("d", path);
// Path around regions
svg.append("path")
.datum(topojson.mesh(data, data.objects.eer, function(a, b) { return a !== b; }))
.attr("d", path)
.attr("class", "subunit-boundary");
// Add places to our data
data.objects.places = {
type: "GeometryCollection",
geometries: [
{
type: "Point",
coordinates: [-0.127758, 51.507351], // London
properties: {
name: "London - Testing"
}
}
]
};
// try plotting a point
svg.append("path")
.datum(topojson.feature(data, data.objects.places))
.attr("d", path)
.attr("class", "place-online");
console.log(data);
});
In a TopoJSON, those numbers in the coordinates are not the actual latitude/longitude values. They have to be transformed. This function transforms the quantized topology to absolute coordinates:
function transformPoint(topology, position) {
position = position.slice();
position[0] = position[0] * topology.transform.scale[0]
+ topology.transform.translate[0],
position[1] = position[1] * topology.transform.scale[1]
+ topology.transform.translate[1]
return position;
};
You'll find the scale and translate at the end of the TopoJSON you linked:
"transform":
{"scale":
[0.000818229038834542,0.0005946917122888551],
"translate":[-6.418556211736409,49.8647494628352]
}
Based on that function, I believe it's easy to write a function that does the reverse:
function transformPointReversed(topology, position) {
position = position.slice();
position[0] = (position[0] - topology.transform.translate[0])
/(topology.transform.scale[0]),
position[1] = (position[1] - topology.transform.translate[1])
/(topology.transform.scale[1])
return position;
};
I tried this function I just made and your London coordinates returned me this array:
[7688.309645789168, 2762.1059840278253]
Please, test it in your coordinates to see if it works.
An alternative is overlaying your TopoJSON with a GeoJSON, which does use an absolute coordinates system.
Here is the API reference: https://github.com/mbostock/topojson-specification/blob/master/README.md#22-geometry-objects
Related
I'm trying to plot a few points onto a map using the D3 geo library based on latitudes and longitudes. However, when I pass these values into my projection function, it results in coordinates that our outside the bounds of my SVG image. My code is based on this example provided in the documentation.
I've thrown the current code up at: http://bl.ocks.org/rpowelll/8312317
My source data is a simple array of objects formatted like so
var places = [
{
name: "Wollongong, Australia",
location: {
latitude: -34.42507,
longitude: 150.89315
}
},
{
name: "Newcastle, Australia",
location: {
latitude: -32.92669,
longitude: 151.77892
}
}
]
Following this I set up an Plate Carrée projection like so:
var width = 960,
height = 480
var projection = d3.geo.equirectangular()
.scale(153)
.translate([width / 2, height / 2])
.precision(.1);
var path = d3.geo.path()
.projection(projection)
From there I draw the map with code effectively identical to the linked example. At the end of my script, I use the following code to plot points on this map:
svg.selectAll(".pin")
.data(places)
.enter().append("circle", ".pin")
.attr("r", 5)
.attr("transform", function(d) {
return "translate(" + projection([
d.location.latitude,
d.location.longitude
]) + ")"
})
However this code results in points that are outside of the SVG element's bounds. Is there anything obvious I'm doing wrong here?
You have a simple typo in your code -- coordinates should be passed as (longitude, latitude) to the projection, not the other way round. This code should work fine:
svg.selectAll(".pin")
.data(places)
.enter().append("circle", ".pin")
.attr("r", 5)
.attr("transform", function(d) {
return "translate(" + projection([
d.location.longitude,
d.location.latitude
]) + ")";
});
I am working on a project where I am trying to visualize data from a database onto a leaflet map. The data contains long/lat coodrinates, but when I'm trying to draw them onto the map, the location is all wrong or not showing at all.
I've read that the lat / long coordinates are not the same coordinates that are being used on the leaflet map, so i'm trying to translate them but with no luck.
The datebase has this structure (.csv file):
datetime,city,state,country,shape,durationSeconds,durationHours,comments,date_posted,latitude,longitude
10/10/1949 20:30,san marcos,tx,us,cylinder,2700,45 minutes,"This event took place in early fall around 1949-50. It occurred after a Boy Scout meeting in the Baptist Church. The Baptist Church sit",4/27/2004,29.8830556,-97.9411111
10/10/1949 21:00,lackland afb,tx,,light,7200,1-2 hrs,"1949 Lackland AFB, TX. Lights racing across the sky & making 90 degree turns on a dime.",12/16/2005,29.38421,-98.581082
and the code so far looks like this:
var map = L.map('mapid').setView([10, 15], 2.2); L.tileLayer('https://api.mapbox.com/styles/v1/josecoto/civ8gwgk3000a2ipdgnsscnai/'
+'tiles/256/{z}/{x}/{y}?access_token=pk.eyJ1Ijoiam9zZWNvdG8iLCJhIjoiY2l2OGZxZWNuMDAxODJ6cGdhcGFuN2IyaCJ9.7szLs0lc_2EjX6g21HI_Kg', {
maxZoom: 18,
id: 'mapbox.streets',
accessToken: 'your.mapbox.access.token'
}).addTo(map);
var w = $("#mapid").width();
var h = $("#mapid").height();
var projection = d3.geoMercator()
.scale(w / 2 / Math.PI)
.translate([w / 2, h / 2])
function latLong(x,y)
{
//console.log(x + ' and ' + y);
var point = map.latLngToLayerPoint(new L.LatLng(y, x));
return point;
}
var svg_map = d3.select(map.getPanes().overlayPane)
.append("svg")
.attr("width", w)
.attr("height", h);
svg_map.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
var coords = projection([d.longitude, d.latitude]);
return coords[0];
//var coords = latLong(d.longitude, d.latitude);
//return coords.x;
})
.attr("cy", function(d) {
var coords = projection([d.longitude, d.latitude]);
return coords[1];
//var coords = latLong(d.longitude, d.latitude);
//return coords.y;
})
.attr("r", function(d) {
return 2;
})
When I'm using the geoMercator() function, I can see the dots displayed on the map, however they are being projected onto the map wrong (because they are being bent from the function or something). And when I'm trying to use the latLong() function, they are not being displayed at all (but no errors in debugger).
I can add that I am getting the values from the database correctly, but the problem is in the vizualisation. Can you guys spot anything?
Thank you in advance!
EDIT: Here's an image of how it looks when I'm using the geoMercator:
i want to draw on map base on longitude and latitude in csv file called tree.csv on a map that i using an image .
My csv file include many lines ,so i will just put some lines here
Longitude Latitude
37.7295482207565 122.392689419827
37.8030467266869 122.425063628702
......
Here is my code
d3.csv("/trees.csv", function(data) {
dataset=data.map(function(d) { return [+d["Longitude"],+d["Latitude"] ];});
console.log(data)
var width = 750,
height = width;
// Set up projection that map is using
var projection = d3.geo.mercator()
.center([-122.433701, 37.767683])
.scale(225000)
.translate([width / 2, height / 2]);
var path=d3.geo.path().projection(projection);
var svgContainer=d3.select("body").append("svg")
.attr("width",width)
.attr("height",height);
svgContainer.append("image")
.attr("width", width)
.attr("height", height)
.attr("xlink:href", "/Ilu.svg");
var trees=svgContainer.selectAll("circles")
.data(data).enter()
.append("circles")
var treesAttributes=trees
.attr("cx",function(d) { return projection(d["Longitude"])[0];})
.attr("cy",function(d) { return projection(d["Latitude"])[1];})
.attr("r","100px")
.style("fill","red");
I can see my map but i cant see any points on my map . When i inspect the web. i see that cx is Nan number ,and cy is same number. I think maybe my array havent been read yet. But i am not sure about the problems. I have been stucked. Can you guys solve me the problem ? Thank you
Your problem lies in that you aren't providing coordinates to be projected.
A d3 geoProjection takes a longitude latitude pair and projects it to an x,y svg coordinate (a projection returns a coordinate as: [x,y], which is why you use this form in your code: projection(coord)[0] to get the cx value). You are seeking to project only a longitude, and then only a latitude:
.attr("cx",function(d) { return projection(d["Longitude"])[0];})
.attr("cy",function(d) { return projection(d["Latitude"])[1];})
In this case, projection won't return an svg coordinate as you aren't providing a geographical coordinate to project. You need to project both longitude and latitude, becuase x and y values produced in a projection are usually (not always) co-dependent - in any conical projection for example, the output y (or x) value is dependent on both latitude and longitude. Further, as projection() returns [x,y], it requires both longitude and latitude for every projection.
Instead try:
.attr("cx",function(d) { return projection([d["Longitude"],d["Latitude"]])[0];})
.attr("cy",function(d) { return projection([d["Longitude"],d["Latitude"]])[1];})
Remeber that d3 geoprojections expect the form: projection([longitude, latitude]), changing the order of longitude and latitude will produce unexpected results.
var data = [
{longitude:1,latitude:1},
{longitude:-1,latitude:1},
{longitude:1,latitude:-1},
{longitude:-1,latitude:-1}
]
var svg = d3.select("body")
.append("svg")
.attr("width",200)
.attr("height",200);
var projection = d3.geoMercator()
.translate([100,100]);
var circles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx",function(d) { return projection([d.longitude,d.latitude])[0];
})
.attr("cy",function(d) { return projection([d["longitude"],d["latitude"]])[1];
})
.attr("r",2)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
(sorry for my english bad level)
Hi I'm using D3 for the first time with mithril js. The map is ok but I have a problem with colors of provinces and it comes from the 'd' attribute to get the id of provinces.The attribute is undefined and I don't understand what is 'd' exactly. is mithril the problem? is there an other way to get 'd' attribute?
controller.map = function(el){
var width = 1160;
var height = 960;
var scale = 10000;
var offset = [width / 2, height / 2];
var center = [0, 50.64];
var rotate = [-4.668, 0];
var parallels = [51.74, 49.34];
var projection = d3.geo.albers()
.center(center)
.rotate(rotate)
.parallels(parallels)
.scale(scale)
.translate(offset)
;
var path = d3.geo.path()
.projection(projection)
;
var svg = d3.select(el).append("svg")
.attr("width",width)
.attr("height",height)
;
d3.json("belprov.json",function(error,be){
if (error) return console.error(error);
var bounds = path.bounds(topojson.feature(be, be.objects.subunits));
var hscale = scale*width / (bounds[1][0] - bounds[0][0]);
var vscale = scale*height / (bounds[1][1] - bounds[0][1]);
scale = (hscale < vscale) ? hscale : vscale;
offset = [width - (bounds[0][0] + bounds[1][0])/2,
height - (bounds[0][1] + bounds[1][1])/2];
var centroid = d3.geo.centroid(topojson.feature(be, be.objects.subunits));
center = [0, centroid[1]];
rotate = [-centroid[0],0];
projection = d3.geo.albers()
.center(center)
.rotate(rotate)
.parallels(parallels)
.scale(scale)
.translate(offset);
svg.selectAll(".province")
.data(topojson.feature(be, be.objects.provinces).features)
.enter().append("path")
.attr("class", function(d) { return "province " + d.id })
.attr("d", path)
;
})
};
The "d" attribute in a path object defines the successive coordinates of the points through which the path has to go (it also gives indication about whether the path should use bezier curves, straight lines, etc.). See some documentation here.
Be careful: in d3, d is often used as a parameter for anonymous functions representing the data currently binded to the current element. So the two are completely different things.
Here, your line
.attr("d", path)
should probably look more like
.attr("d", function(d){return d.path})
i.e., take the field path within the data elements.
You can do something like this to color diffrent paths:
//make a color scale
var color20 = d3.scale.category20();
//your code as you doing
//on making paths do
svg.selectAll(".province")
.data(topojson.feature(be, be.objects.provinces).features)
.enter().append("path")
.attr("class", function(d) { return "province " + d.id })
.style("fill", function(d){return color(d.id);})//do this to color path based on id.
.attr("d", path)
I am using this approach to draw points on a Map with a Orthographic projection:
var path = d3.geo.path()
.projection(…)
.pointRadius(function(d) { return d.radius; });
svg.selectAll("path.point")
.data(data)
.enter().append("path")
.datum(function(d) {
return {type: "Point", coordinates: [d.Lon, d.Lat], radius: d.Magnitude};
})
.attr("class", "point")
.attr("d", path);
This works well.
What can be done to make these points appear as triangles instead of circles?
You can use d3.svg.symbol().
//define triangle
var arc = d3.svg.symbol().type('triangle-up');
// put a triangle on every city
svg.selectAll(".tripath")
.data(topojson.feature(uk, uk.objects.places).features)
.enter().append("path")
.attr('d',arc)
.attr("transform", function(d) {
return "translate(" + projection(d.geometry.coordinates) + ")"; });
There are some predefined symbols:
circle - a circle.
cross - a Greek cross or plus sign.
diamond - a rhombus.
square - an axis-aligned square.
triangle-down - a downward-pointing equilateral triangle.
triangle-up - an upward-pointing equilateral triangle.
Here you have Mike's Wiki: https://github.com/mbostock/d3/wiki/SVG-Shapes#symbol
And here you can see a working code: http://jsfiddle.net/6d3ansfn/
Here is solution for generating triangle-markers using a path geometry object. The two initial coordinates as center points for each triangle.
let halfWidth = 0.15;
points.enter().append('path')
.datum((d) => {
let pointA = [(Number(d.coord[0]) - halfWidth).toString(), (Number(d.coord[1]) - halfWidth).toString()];
let pointB = [d.coord[0], (Number(d.coord[1]) + halfWidth).toString()];
let pointC = [(Number(d.coord[0]) + halfWidth).toString(), (Number(d.coord[1]) - halfWidth).toString()];
polypoints = [
[pointA, pointB, pointC, pointA]
];
return {
type: 'Polygon',
coordinates: polypoints,
id: d.id,
center: d.coord,
};
});