scaling d3.js projections - geospatial

I'm trying to draw a map of NYC using d3. I've downloaded a bunch of shapefiles from http://www.nyc.gov/html/dcp/html/bytes/dwndistricts.shtml. I've converted them to geoJSON using http://converter.mygeodata.eu/, such that they're in WGS 84 (aka, I think, latitude and longitude).
I'm pretty sure the geoJSON is valid: I can plot it using matplotlib and it looks like NYC, and the numbers seem to be in a valid range. Here's my javascript:
var path = d3.geo.path()
d3.select("body")
.append("svg")
.attr("height", 1000)
.attr("width", 1000)
.append("g")
.selectAll("path")
.data(data.features)
.enter()
.append("path")
.attr("d", path)
.style("stroke","black")
.style("stroke-width","1px")
This plots, as advertised, the Albers projection of NYC. Trouble is, I think, that the scale of the projection is chosen so that the US fits onto a nice webpage, making the paths for NYC a little squiggle at the right-hand-side of the screen.
What's the 'correct' way (lemme try to claim to be the first to say d3onic) to scale a geo.path() such that the extents of my lat/lon scale onto the width and height of my SVG?
(little disclaimer: apologies if I've missed something obvious, this is for a project I'm trying to complete at the extreme ends of the day)

First, you'll want to create a projection, and assign that to the d3.geo.path, so that you can customize the projection settings.
var albers = d3.geo.albers(),
path = d3.geo.path().projection(albers);
The default projection is d3.geo.albersUsa, which is actually a composite projection (with four different discontinuous areas) designed for showing the 48 states, Alaska, Hawaii and Puerto Rico. Ah, ethnocentrism. ;)
Use the albers example in the git repository to determine the correct projection settings interactively. The settings you need to set are:
the origin should be NYC's latitude and longitude (perhaps 73.98°, 40.71°)
the translate should be the center of your display area (so, if you're drawing something 960×500, you can use the default 480,250; this will be the pixel location of the origin)
the scale is some number that specifies how much to zoom-in; since you're drawing a city-scale map, you probably want a value more like 10000
Lastly, you'll need to pick some parallels. You can use the defaults provided by d3.geo.albers(), but there might be more suitable ones for NYC. Possibly check with the USGS, because they often publish standard parallels for different map areas.

Related

Recalculating path's array after modification - fabricjs

After moving, rescaling or rotating a path in fabricjs, I would like to recalculate the path's array coordinates in order to use them later to draw the exact same path on a plain HTML5 canvas.
I have gotten around rotation by storing the angle and then performing a transformation of the whole html canvas. For translation I tried recalculating the points like so:
point[1] = (point[1] + modifiedObject.left - oldState.left);
point[2] = (point[2] + modifiedObject.top - oldState.top);
but this messes up with rescaling on fabric.
I also tried using the transformation matrix on the points, but this didn't work either and I think it's because I am missing some steps in between.
Any help would be appreciated.
Here's a simple demo of what I'm trying to do in essence: https://jsfiddle.net/1b68eLdr/86688/

Applying rotation to Graphics object

I have very little experience programming with graphics objects. I am currently tasked with exporting a document (.tiff image) with redacted annotations. The redacted annotation is just a black rectangle object. I am able to get the x coordinates, y coordinates, width and height properties through the .XMP data. There is also a property called rotatation. This is where I am getting stuck, applying the rotation.
So, imagine a document with a redaction on it blacking out the first paragraph. Then, using a tool in the editor the user rotates the document so that it is now laying on it's side. The client is able to render the redaction correctly because we are using the Atalasoft controls to get and display annotations. Now we have a web service that will go and retrieve that image with redactions. We are not able to use the Atalasoft controls in this service due to licensing issues so we just extract the .XMP data from the .tiff image and manually draw the redactions. The problem is, if the user rotates the document when the redaction is already on the document I am having a hard time getting the redaction to rotate correctly (due to my lack of knowledge on graphics programming). If I do not apply any rotation, the redaction is displayed where it was BEFORE the document had been rotated, thus redacting the wrong area of the document.
Here is what I have tried:
Dim rectangle As New Rectangle(xCoordinate, yCoordinate, width, height)
graphics.RotateTransform(rotation)
graphics.FillRectangle(Brushes.Black, rectangle)
When I do this, the redaction does not show up at all on the final document. I have read that I may need to call the following before applying the rotation:
graphics.TranslateTransform(x,y)
But I have no idea what I should be passing in as x and y. It seems like I just need to get the rotation to apply from the upper left corner of the rectangle, but I have yet to figure out a way to properly do this.
Thank you so much for any help or pushes in the right direction!
EDIT 1:
I have also tried this (taken from How can I rotate an RectangleF at a specific degree using Graphics object?).
Dim rectangle As New Rectangle(xCoordinate, yCoordinate, width, height)
Using rotationMatrix As New Matrix
rotationMatrix.RotateAt(rotation, New PointF(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)))
graphics.Transform = rotationMatrix
graphics.FillRectangle(Brushes.Black, rectangle)
graphics.ResetTransform()
End Using
Which does rotate the rectangle, but it ends up in the wrong spot so it is not redacting the correct portion of the document. Once again, when I display the document without any rotation transform, it looks like the redaction simply needs to be rotated using the upper left corner as an axis but I'm not quite sure how to accomplish that.
Figured it out. Here is how I am rotating a rectangle using the upper left-hand corner as the axis:
Dim rectangle As New Rectangle(xCoordinate, yCoordinate, width, height)
Using rotationMatrix As New Matrix
rotationMatrix.RotateAt(rotation, New PointF(rectangle.Left, rectangle.Top))
graphics.Transform = rotationMatrix
graphics.FillRectangle(Brushes.Black, rectangle)
graphics.ResetTransform()
End Using

Is it possible to translate an SVG path to GeoJSON?

My objective here is to allow users to specify territories or regions given a background world-map overlay, which is an SVG generated from GeoJSON data using D3. I have done the part where the territories' points are pinpointed by the user, and an SVG is generated. This works well.
Now I would like to save the territory's coordinates, using the background map's projection, scale and translation. I saw a lot of documentation about translating GeoJSON data to SVG s, but nothing about the other way. Is it even possible ?
Thanks Ben Lyall, eventually I used the native SVG functions getTotalLength() and getPointAtLength() to convert my path to an array of top/left positions (in pixels), then d3's projection.invert() to translate them into coordinates.

How do I rotate or scale (transform) an SVG path relative to its center point?

I'm trying to rotate and scale shapes within an SVG around their center point. I've looked into several libraries, including Jquery, Greensock, D3, RaphaelJS, but I haven't been able to find any that provide a straightforward way to accomplish this. Each animates the shape from the origin point (which I understand is the default). I want to be able to spin a shape around its center point or scale it up or down from the center point.
Here are a couple examples using Greensock and D3 that illustrate the default behavior: http://jsbin.com/AHEXiPa/1/edit?html,js,output
Each of these examples bounce in and out from the top left as opposed to remaining stationary and expanding from the center of the triangle out in all directions.
Can one of the libraries I mentioned accomplish this, or is there another library or method I should consider?
Ideally, I need to be able to apply the animation/transform to an existing object in the DOM. D3 is good at this for instance, but Raphael seems to require converting an SVG to Raphael first prior to injecting it into the DOM.
Really its a case of pick the library that suits your needs, and then you will figure a way. As BigBadaboom says, if you do a search, there are lots of solutions.
To try and combine your questions, as sometimes the tricky bit is using an existing DOM object, I've included an example in Snap.svg. You can often do something similar in most libraries.
jsfiddle here Fiddle using your existing html.
s = Snap("#mySVGContainer1"); // create a canvas from existing svg
var triangle1 = s.select("#myShape1").transform("r90"); //select&transform existing object
p = Snap("#mySVGContainer2");
var triangle2 = p.select("#myShape2");
var bbox = triangle2.getBBox(); //bounding box, centre cx/cy
//rotate and scale with transform string (raphael/snap format)
triangle2.animate({ transform: "r180," + bbox.cx + ',' + bbox.cy + "s3,3," + bbox.cx + "," + bbox.cy }, 2000);
For rotations, as #Ian points out, you can specify the center of rotation. For other transformations, changes are defined relative to the path's (0,0) point.
The easiest way to get transformations to work relative to the path's center is to either:
Define the path so that it is centered around the (0,0) point; or
Wrap the path in a <g> element, and then translate it so it is centered on the (0,0) point of the <g> element's coordinate system.
Then, you can apply rotations, scales and transforms (on the <g> element, if using) and they will all be nicely centred.
The trickiest part is figuring out the "center" of an arbitrary shape. #Ian's approach of using the center of the bounding box will usually give decent results. If your shape is a polygon there are d3 functions you could use.
Example showing a shape moving with the mouse, rotating and changing scale, all centered around the center of the bounding box:
http://fiddle.jshell.net/LgfE3/
Edit: simplier jsfiddle
I've been looking for a long time, and will settle for the following.
1. Design your svg shape at coordinate x:0,y:0.
2. Identify by hand the center of rotation, by example, center = [ x:50,y:100].
3. Build a spinIt() function such :
function spinIt() {
needle.transition()
.duration(2000)
.attrTween("transform", tween);
function tween() {
return d3.interpolateString("rotate(-180, 50, 100)", "rotate(90, 50, 100)");
}
}
4. Use it on a triger:
svg.on("click", spinIt);
http://jsfiddle.net/SHF2M/79/

D3 Sunburst clip path of text

I am trying to implement a d3 visualization based on the sunburst diagram, and i have found an almost perfect online example of this which i have got working http://tributary.io/inlet/4127332/:
My main issue is that I need to also Clip the text to the segment,I have tried using the svg clip path but my meager d3 skills have let me down. Any help with this is appreciated.
So my first attempt to clip the text did not work and I think this is because the arc's coordinate space does not line up with the text's coordinate space in the way that you want if you are using the arc generator, as you are.
I found that if I apply the clip to the groups you make for each node then it worked like a charm. There was one caveat. When I tried generating my clip path and then applying them the order that the nodes were joined to the elements differed and so the wrong path were clipping the wrong text. I got around this by adding an id to each data element. You can see the final version here
The important parts are adding the clip paths (note the use of the new id field):
svg.append('defs')
.selectAll("clipPath")
.data(partition.nodes)
.enter().append('svg:clipPath')
.attr('id', function(d,i) { return d.id;})
.append('path').attr('d', arc);
Then you simply have to reference them on your node groups (again using the id):
group =
svg.selectAll("g")
.data(partition.nodes)
.enter().append('svg:g')
.attr('clip-path', function(d,i) { return 'url(#' + d.id + ')';});
In the tributary I put the svg data join first so that the "defs" node would appear in the usual place (first after the svg tag), but I do not think this is technically necessary.

Resources