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 d3_geo_pathType(pathTypes, d); |
---|
18 | } |
---|
19 | |
---|
20 | function project(coordinates) { |
---|
21 | return projection(coordinates).join(","); |
---|
22 | } |
---|
23 | |
---|
24 | var pathTypes = { |
---|
25 | |
---|
26 | FeatureCollection: function(f) { |
---|
27 | var path = [], |
---|
28 | features = f.features, |
---|
29 | i = -1, // features.index |
---|
30 | n = features.length; |
---|
31 | while (++i < n) path.push(d3_geo_pathType(pathTypes, features[i].geometry)); |
---|
32 | return path.join(""); |
---|
33 | }, |
---|
34 | |
---|
35 | Feature: function(f) { |
---|
36 | return d3_geo_pathType(pathTypes, f.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 | m = subcoordinates.length; |
---|
93 | path.push("M"); |
---|
94 | while (++j < m) path.push(project(subcoordinates[j]), "L"); |
---|
95 | path[path.length - 1] = "Z"; |
---|
96 | } |
---|
97 | return path.join(""); |
---|
98 | }, |
---|
99 | |
---|
100 | MultiPolygon: function(o) { |
---|
101 | var path = [], |
---|
102 | coordinates = o.coordinates, |
---|
103 | i = -1, // coordinates index |
---|
104 | n = coordinates.length, |
---|
105 | subcoordinates, // coordinates[i] |
---|
106 | j, // subcoordinates index |
---|
107 | m, // subcoordinates.length |
---|
108 | subsubcoordinates, // subcoordinates[j] |
---|
109 | k, // subsubcoordinates index |
---|
110 | p; // subsubcoordinates.length |
---|
111 | while (++i < n) { |
---|
112 | subcoordinates = coordinates[i]; |
---|
113 | j = -1; |
---|
114 | m = subcoordinates.length; |
---|
115 | while (++j < m) { |
---|
116 | subsubcoordinates = subcoordinates[j]; |
---|
117 | k = -1; |
---|
118 | p = subsubcoordinates.length - 1; |
---|
119 | path.push("M"); |
---|
120 | while (++k < p) path.push(project(subsubcoordinates[k]), "L"); |
---|
121 | path[path.length - 1] = "Z"; |
---|
122 | } |
---|
123 | } |
---|
124 | return path.join(""); |
---|
125 | }, |
---|
126 | |
---|
127 | GeometryCollection: function(o) { |
---|
128 | var path = [], |
---|
129 | geometries = o.geometries, |
---|
130 | i = -1, // geometries index |
---|
131 | n = geometries.length; |
---|
132 | while (++i < n) path.push(d3_geo_pathType(pathTypes, geometries[i])); |
---|
133 | return path.join(""); |
---|
134 | } |
---|
135 | |
---|
136 | }; |
---|
137 | |
---|
138 | var areaTypes = { |
---|
139 | |
---|
140 | FeatureCollection: function(f) { |
---|
141 | var area = 0, |
---|
142 | features = f.features, |
---|
143 | i = -1, // features.index |
---|
144 | n = features.length; |
---|
145 | while (++i < n) area += d3_geo_pathType(areaTypes, features[i]); |
---|
146 | return area; |
---|
147 | }, |
---|
148 | |
---|
149 | Feature: function(f) { |
---|
150 | return d3_geo_pathType(areaTypes, f.geometry); |
---|
151 | }, |
---|
152 | |
---|
153 | Point: d3_geo_pathZero, |
---|
154 | MultiPoint: d3_geo_pathZero, |
---|
155 | LineString: d3_geo_pathZero, |
---|
156 | MultiLineString: d3_geo_pathZero, |
---|
157 | |
---|
158 | Polygon: function(o) { |
---|
159 | return polygonArea(o.coordinates); |
---|
160 | }, |
---|
161 | |
---|
162 | MultiPolygon: function(o) { |
---|
163 | var sum = 0, |
---|
164 | coordinates = o.coordinates, |
---|
165 | i = -1, // coordinates index |
---|
166 | n = coordinates.length; |
---|
167 | while (++i < n) sum += polygonArea(coordinates[i]); |
---|
168 | return sum; |
---|
169 | }, |
---|
170 | |
---|
171 | GeometryCollection: function(o) { |
---|
172 | var sum = 0, |
---|
173 | geometries = o.geometries, |
---|
174 | i = -1, // geometries index |
---|
175 | n = geometries.length; |
---|
176 | while (++i < n) sum += d3_geo_pathType(areaTypes, geometries[i]); |
---|
177 | return sum; |
---|
178 | } |
---|
179 | |
---|
180 | }; |
---|
181 | |
---|
182 | function polygonArea(coordinates) { |
---|
183 | var sum = area(coordinates[0]), // exterior ring |
---|
184 | i = 0, // coordinates.index |
---|
185 | n = coordinates.length; |
---|
186 | while (++i < n) sum -= area(coordinates[i]); // holes |
---|
187 | return sum; |
---|
188 | } |
---|
189 | |
---|
190 | function polygonCentroid(coordinates) { |
---|
191 | var polygon = d3.geom.polygon(coordinates[0].map(projection)), // exterior ring |
---|
192 | centroid = polygon.centroid(1), |
---|
193 | x = centroid[0], |
---|
194 | y = centroid[1], |
---|
195 | z = Math.abs(polygon.area()), |
---|
196 | i = 0, // coordinates index |
---|
197 | n = coordinates.length; |
---|
198 | while (++i < n) { |
---|
199 | polygon = d3.geom.polygon(coordinates[i].map(projection)); // holes |
---|
200 | centroid = polygon.centroid(1); |
---|
201 | x -= centroid[0]; |
---|
202 | y -= centroid[1]; |
---|
203 | z -= Math.abs(polygon.area()); |
---|
204 | } |
---|
205 | return [x, y, 6 * z]; // weighted centroid |
---|
206 | } |
---|
207 | |
---|
208 | var centroidTypes = { |
---|
209 | |
---|
210 | // TODO FeatureCollection |
---|
211 | // TODO Point |
---|
212 | // TODO MultiPoint |
---|
213 | // TODO LineString |
---|
214 | // TODO MultiLineString |
---|
215 | // TODO GeometryCollection |
---|
216 | |
---|
217 | Feature: function(f) { |
---|
218 | return d3_geo_pathType(centroidTypes, f.geometry); |
---|
219 | }, |
---|
220 | |
---|
221 | Polygon: function(o) { |
---|
222 | var centroid = polygonCentroid(o.coordinates); |
---|
223 | return [centroid[0] / centroid[2], centroid[1] / centroid[2]]; |
---|
224 | }, |
---|
225 | |
---|
226 | MultiPolygon: function(o) { |
---|
227 | var area = 0, |
---|
228 | coordinates = o.coordinates, |
---|
229 | centroid, |
---|
230 | x = 0, |
---|
231 | y = 0, |
---|
232 | z = 0, |
---|
233 | i = -1, // coordinates index |
---|
234 | n = coordinates.length; |
---|
235 | while (++i < n) { |
---|
236 | centroid = polygonCentroid(coordinates[i]); |
---|
237 | x += centroid[0]; |
---|
238 | y += centroid[1]; |
---|
239 | z += centroid[2]; |
---|
240 | } |
---|
241 | return [x / z, y / z]; |
---|
242 | } |
---|
243 | |
---|
244 | }; |
---|
245 | |
---|
246 | |
---|
247 | function area(coordinates) { |
---|
248 | return Math.abs(d3.geom.polygon(coordinates.map(projection)).area()); |
---|
249 | } |
---|
250 | |
---|
251 | path.projection = function(x) { |
---|
252 | projection = x; |
---|
253 | return path; |
---|
254 | }; |
---|
255 | |
---|
256 | path.area = function(d) { |
---|
257 | return d3_geo_pathType(areaTypes, d); |
---|
258 | }; |
---|
259 | |
---|
260 | path.centroid = function(d) { |
---|
261 | return d3_geo_pathType(centroidTypes, d); |
---|
262 | }; |
---|
263 | |
---|
264 | path.pointRadius = function(x) { |
---|
265 | if (typeof x === "function") pointRadius = x; |
---|
266 | else { |
---|
267 | pointRadius = +x; |
---|
268 | pointCircle = d3_path_circle(pointRadius); |
---|
269 | } |
---|
270 | return path; |
---|
271 | }; |
---|
272 | |
---|
273 | return path; |
---|
274 | }; |
---|
275 | |
---|
276 | function d3_path_circle(radius) { |
---|
277 | return "m0," + radius |
---|
278 | + "a" + radius + "," + radius + " 0 1,1 0," + (-2 * radius) |
---|
279 | + "a" + radius + "," + radius + " 0 1,1 0," + (+2 * radius) |
---|
280 | + "z"; |
---|
281 | } |
---|
282 | |
---|
283 | function d3_geo_pathZero() { |
---|
284 | return 0; |
---|
285 | } |
---|
286 | |
---|
287 | function d3_geo_pathType(types, o) { |
---|
288 | return o && o.type in types ? types[o.type](o) : ""; |
---|
289 | } |
---|