|
1 /** |
|
2 * Returns a function that, given a GeoJSON object (e.g., a feature), returns |
|
3 * the corresponding SVG path. The function can be customized by overriding the |
|
4 * projection. Point features are mapped to circles with a default radius of |
|
5 * 4.5px; the radius can be specified either as a constant or a function that |
|
6 * is evaluated per object. |
|
7 */ |
|
8 d3.geo.path = function() { |
|
9 var pointRadius = 4.5, |
|
10 pointCircle = d3_path_circle(pointRadius), |
|
11 projection = d3.geo.albersUsa(); |
|
12 |
|
13 function path(d, i) { |
|
14 if (typeof pointRadius === "function") { |
|
15 pointCircle = d3_path_circle(pointRadius.apply(this, arguments)); |
|
16 } |
|
17 return pathType(d) || null; |
|
18 } |
|
19 |
|
20 function project(coordinates) { |
|
21 return projection(coordinates).join(","); |
|
22 } |
|
23 |
|
24 var pathType = d3_geo_type({ |
|
25 |
|
26 FeatureCollection: function(o) { |
|
27 var path = [], |
|
28 features = o.features, |
|
29 i = -1, // features.index |
|
30 n = features.length; |
|
31 while (++i < n) path.push(pathType(features[i].geometry)); |
|
32 return path.join(""); |
|
33 }, |
|
34 |
|
35 Feature: function(o) { |
|
36 return pathType(o.geometry); |
|
37 }, |
|
38 |
|
39 Point: function(o) { |
|
40 return "M" + project(o.coordinates) + pointCircle; |
|
41 }, |
|
42 |
|
43 MultiPoint: function(o) { |
|
44 var path = [], |
|
45 coordinates = o.coordinates, |
|
46 i = -1, // coordinates.index |
|
47 n = coordinates.length; |
|
48 while (++i < n) path.push("M", project(coordinates[i]), pointCircle); |
|
49 return path.join(""); |
|
50 }, |
|
51 |
|
52 LineString: function(o) { |
|
53 var path = ["M"], |
|
54 coordinates = o.coordinates, |
|
55 i = -1, // coordinates.index |
|
56 n = coordinates.length; |
|
57 while (++i < n) path.push(project(coordinates[i]), "L"); |
|
58 path.pop(); |
|
59 return path.join(""); |
|
60 }, |
|
61 |
|
62 MultiLineString: function(o) { |
|
63 var path = [], |
|
64 coordinates = o.coordinates, |
|
65 i = -1, // coordinates.index |
|
66 n = coordinates.length, |
|
67 subcoordinates, // coordinates[i] |
|
68 j, // subcoordinates.index |
|
69 m; // subcoordinates.length |
|
70 while (++i < n) { |
|
71 subcoordinates = coordinates[i]; |
|
72 j = -1; |
|
73 m = subcoordinates.length; |
|
74 path.push("M"); |
|
75 while (++j < m) path.push(project(subcoordinates[j]), "L"); |
|
76 path.pop(); |
|
77 } |
|
78 return path.join(""); |
|
79 }, |
|
80 |
|
81 Polygon: function(o) { |
|
82 var path = [], |
|
83 coordinates = o.coordinates, |
|
84 i = -1, // coordinates.index |
|
85 n = coordinates.length, |
|
86 subcoordinates, // coordinates[i] |
|
87 j, // subcoordinates.index |
|
88 m; // subcoordinates.length |
|
89 while (++i < n) { |
|
90 subcoordinates = coordinates[i]; |
|
91 j = -1; |
|
92 if ((m = subcoordinates.length - 1) > 0) { |
|
93 path.push("M"); |
|
94 while (++j < m) path.push(project(subcoordinates[j]), "L"); |
|
95 path[path.length - 1] = "Z"; |
|
96 } |
|
97 } |
|
98 return path.join(""); |
|
99 }, |
|
100 |
|
101 MultiPolygon: function(o) { |
|
102 var path = [], |
|
103 coordinates = o.coordinates, |
|
104 i = -1, // coordinates index |
|
105 n = coordinates.length, |
|
106 subcoordinates, // coordinates[i] |
|
107 j, // subcoordinates index |
|
108 m, // subcoordinates.length |
|
109 subsubcoordinates, // subcoordinates[j] |
|
110 k, // subsubcoordinates index |
|
111 p; // subsubcoordinates.length |
|
112 while (++i < n) { |
|
113 subcoordinates = coordinates[i]; |
|
114 j = -1; |
|
115 m = subcoordinates.length; |
|
116 while (++j < m) { |
|
117 subsubcoordinates = subcoordinates[j]; |
|
118 k = -1; |
|
119 if ((p = subsubcoordinates.length - 1) > 0) { |
|
120 path.push("M"); |
|
121 while (++k < p) path.push(project(subsubcoordinates[k]), "L"); |
|
122 path[path.length - 1] = "Z"; |
|
123 } |
|
124 } |
|
125 } |
|
126 return path.join(""); |
|
127 }, |
|
128 |
|
129 GeometryCollection: function(o) { |
|
130 var path = [], |
|
131 geometries = o.geometries, |
|
132 i = -1, // geometries index |
|
133 n = geometries.length; |
|
134 while (++i < n) path.push(pathType(geometries[i])); |
|
135 return path.join(""); |
|
136 } |
|
137 |
|
138 }); |
|
139 |
|
140 var areaType = path.area = d3_geo_type({ |
|
141 |
|
142 FeatureCollection: function(o) { |
|
143 var area = 0, |
|
144 features = o.features, |
|
145 i = -1, // features.index |
|
146 n = features.length; |
|
147 while (++i < n) area += areaType(features[i]); |
|
148 return area; |
|
149 }, |
|
150 |
|
151 Feature: function(o) { |
|
152 return areaType(o.geometry); |
|
153 }, |
|
154 |
|
155 Polygon: function(o) { |
|
156 return polygonArea(o.coordinates); |
|
157 }, |
|
158 |
|
159 MultiPolygon: function(o) { |
|
160 var sum = 0, |
|
161 coordinates = o.coordinates, |
|
162 i = -1, // coordinates index |
|
163 n = coordinates.length; |
|
164 while (++i < n) sum += polygonArea(coordinates[i]); |
|
165 return sum; |
|
166 }, |
|
167 |
|
168 GeometryCollection: function(o) { |
|
169 var sum = 0, |
|
170 geometries = o.geometries, |
|
171 i = -1, // geometries index |
|
172 n = geometries.length; |
|
173 while (++i < n) sum += areaType(geometries[i]); |
|
174 return sum; |
|
175 } |
|
176 |
|
177 }, 0); |
|
178 |
|
179 function polygonArea(coordinates) { |
|
180 var sum = area(coordinates[0]), // exterior ring |
|
181 i = 0, // coordinates.index |
|
182 n = coordinates.length; |
|
183 while (++i < n) sum -= area(coordinates[i]); // holes |
|
184 return sum; |
|
185 } |
|
186 |
|
187 function polygonCentroid(coordinates) { |
|
188 var polygon = d3.geom.polygon(coordinates[0].map(projection)), // exterior ring |
|
189 area = polygon.area(), |
|
190 centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1), |
|
191 x = centroid[0], |
|
192 y = centroid[1], |
|
193 z = area, |
|
194 i = 0, // coordinates index |
|
195 n = coordinates.length; |
|
196 while (++i < n) { |
|
197 polygon = d3.geom.polygon(coordinates[i].map(projection)); // holes |
|
198 area = polygon.area(); |
|
199 centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1); |
|
200 x -= centroid[0]; |
|
201 y -= centroid[1]; |
|
202 z -= area; |
|
203 } |
|
204 return [x, y, 6 * z]; // weighted centroid |
|
205 } |
|
206 |
|
207 var centroidType = path.centroid = d3_geo_type({ |
|
208 |
|
209 // TODO FeatureCollection |
|
210 // TODO Point |
|
211 // TODO MultiPoint |
|
212 // TODO LineString |
|
213 // TODO MultiLineString |
|
214 // TODO GeometryCollection |
|
215 |
|
216 Feature: function(o) { |
|
217 return centroidType(o.geometry); |
|
218 }, |
|
219 |
|
220 Polygon: function(o) { |
|
221 var centroid = polygonCentroid(o.coordinates); |
|
222 return [centroid[0] / centroid[2], centroid[1] / centroid[2]]; |
|
223 }, |
|
224 |
|
225 MultiPolygon: function(o) { |
|
226 var area = 0, |
|
227 coordinates = o.coordinates, |
|
228 centroid, |
|
229 x = 0, |
|
230 y = 0, |
|
231 z = 0, |
|
232 i = -1, // coordinates index |
|
233 n = coordinates.length; |
|
234 while (++i < n) { |
|
235 centroid = polygonCentroid(coordinates[i]); |
|
236 x += centroid[0]; |
|
237 y += centroid[1]; |
|
238 z += centroid[2]; |
|
239 } |
|
240 return [x / z, y / z]; |
|
241 } |
|
242 |
|
243 }); |
|
244 |
|
245 function area(coordinates) { |
|
246 return Math.abs(d3.geom.polygon(coordinates.map(projection)).area()); |
|
247 } |
|
248 |
|
249 path.projection = function(x) { |
|
250 projection = x; |
|
251 return path; |
|
252 }; |
|
253 |
|
254 path.pointRadius = function(x) { |
|
255 if (typeof x === "function") pointRadius = x; |
|
256 else { |
|
257 pointRadius = +x; |
|
258 pointCircle = d3_path_circle(pointRadius); |
|
259 } |
|
260 return path; |
|
261 }; |
|
262 |
|
263 return path; |
|
264 }; |
|
265 |
|
266 function d3_path_circle(radius) { |
|
267 return "m0," + radius |
|
268 + "a" + radius + "," + radius + " 0 1,1 0," + (-2 * radius) |
|
269 + "a" + radius + "," + radius + " 0 1,1 0," + (+2 * radius) |
|
270 + "z"; |
|
271 } |