Paul Greyson

implement topojson based map rendering

position devices based on location.lat/lng
use viewbox on topo root svg to support live scaling of map and nodes

Change-Id: I56c2b1e211ab63a694b817d04ee4bb62ac62cec4
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
34 <!-- Third party library code included here --> 34 <!-- Third party library code included here -->
35 <!--TODO: use the minified version of d3, once debugging is complete --> 35 <!--TODO: use the minified version of d3, once debugging is complete -->
36 <script src="libs/d3.js"></script> 36 <script src="libs/d3.js"></script>
37 + <script src="libs/topojson.v1.min.js"></script>
37 <script src="libs/jquery-2.1.1.min.js"></script> 38 <script src="libs/jquery-2.1.1.min.js"></script>
38 39
39 <!-- Base library and framework stylesheets included here --> 40 <!-- Base library and framework stylesheets included here -->
...@@ -101,6 +102,7 @@ ...@@ -101,6 +102,7 @@
101 <!-- Contributed (application) views injected here --> 102 <!-- Contributed (application) views injected here -->
102 <!-- TODO: replace with template marker and inject refs server-side --> 103 <!-- TODO: replace with template marker and inject refs server-side -->
103 <script src="webSockTrace.js"></script> 104 <script src="webSockTrace.js"></script>
105 +
104 <script src="topo2.js"></script> 106 <script src="topo2.js"></script>
105 107
106 <!-- finally, build the UI--> 108 <!-- finally, build the UI-->
......
...@@ -6,18 +6,14 @@ ...@@ -6,18 +6,14 @@
6 "online": false, 6 "online": false,
7 "location": { 7 "location": {
8 "type": "latlng", 8 "type": "latlng",
9 - "lat": 37.6, 9 + "lat": 37.7833,
10 - "lng": 122.3 10 + "lng": -122.4167
11 }, 11 },
12 "labels": [ 12 "labels": [
13 "0000ffffffff0008", 13 "0000ffffffff0008",
14 "FF:FF:FF:FF:00:08", 14 "FF:FF:FF:FF:00:08",
15 "sw-8", 15 "sw-8",
16 "" 16 ""
17 - ], 17 + ]
18 - "metaUi": {
19 - "x": 520,
20 - "y": 350
21 - }
22 } 18 }
23 } 19 }
......
...@@ -10,9 +10,10 @@ ...@@ -10,9 +10,10 @@
10 "sw-3", 10 "sw-3",
11 "" 11 ""
12 ], 12 ],
13 - "metaUi": { 13 + "location": {
14 - "x": 800, 14 + "type": "latlng",
15 - "y": 280 15 + "lat": 40.7127,
16 + "lng": -74.0059
16 } 17 }
17 } 18 }
18 } 19 }
......
1 +see: http://bost.ocks.org/mike/map/
2 +
3 +brew install gdal
4 +npm install -g topojson
5 +
6 +To generate continental US map:
7 +
8 +$ wget 'http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/50m/cultural/ne_50m_admin_1_states_provinces_lakes.zip'
9 +$ unzip ne_50m_admin_1_states_provinces_lakes.zip
10 +$ ogr2ogr -f GeoJSON -where "sr_adm0_a3 IN ('USA')" states.json ne_50m_admin_1_states_provinces_lakes.shp
11 +
12 +edit states.json to remove data for Hawaii and Alaska
13 +
14 +$ topojson states.json > topology.json
15 +
This diff could not be displayed because it is too large.
1 +!function(){function t(n,t){function r(t){var r,e=n.arcs[0>t?~t:t],o=e[0];return n.transform?(r=[0,0],e.forEach(function(n){r[0]+=n[0],r[1]+=n[1]})):r=e[e.length-1],0>t?[r,o]:[o,r]}function e(n,t){for(var r in n){var e=n[r];delete t[e.start],delete e.start,delete e.end,e.forEach(function(n){o[0>n?~n:n]=1}),f.push(e)}}var o={},i={},u={},f=[],c=-1;return t.forEach(function(r,e){var o,i=n.arcs[0>r?~r:r];i.length<3&&!i[1][0]&&!i[1][1]&&(o=t[++c],t[c]=r,t[e]=o)}),t.forEach(function(n){var t,e,o=r(n),f=o[0],c=o[1];if(t=u[f])if(delete u[t.end],t.push(n),t.end=c,e=i[c]){delete i[e.start];var a=e===t?t:t.concat(e);i[a.start=t.start]=u[a.end=e.end]=a}else i[t.start]=u[t.end]=t;else if(t=i[c])if(delete i[t.start],t.unshift(n),t.start=f,e=u[f]){delete u[e.end];var s=e===t?t:e.concat(t);i[s.start=e.start]=u[s.end=t.end]=s}else i[t.start]=u[t.end]=t;else t=[n],i[t.start=f]=u[t.end=c]=t}),e(u,i),e(i,u),t.forEach(function(n){o[0>n?~n:n]||f.push([n])}),f}function r(n,r,e){function o(n){var t=0>n?~n:n;(s[t]||(s[t]=[])).push({i:n,g:a})}function i(n){n.forEach(o)}function u(n){n.forEach(i)}function f(n){"GeometryCollection"===n.type?n.geometries.forEach(f):n.type in l&&(a=n,l[n.type](n.arcs))}var c=[];if(arguments.length>1){var a,s=[],l={LineString:i,MultiLineString:u,Polygon:u,MultiPolygon:function(n){n.forEach(u)}};f(r),s.forEach(arguments.length<3?function(n){c.push(n[0].i)}:function(n){e(n[0].g,n[n.length-1].g)&&c.push(n[0].i)})}else for(var h=0,p=n.arcs.length;p>h;++h)c.push(h);return{type:"MultiLineString",arcs:t(n,c)}}function e(r,e){function o(n){n.forEach(function(t){t.forEach(function(t){(f[t=0>t?~t:t]||(f[t]=[])).push(n)})}),c.push(n)}function i(n){return l(u(r,{type:"Polygon",arcs:[n]}).coordinates[0])>0}var f={},c=[],a=[];return e.forEach(function(n){"Polygon"===n.type?o(n.arcs):"MultiPolygon"===n.type&&n.arcs.forEach(o)}),c.forEach(function(n){if(!n._){var t=[],r=[n];for(n._=1,a.push(t);n=r.pop();)t.push(n),n.forEach(function(n){n.forEach(function(n){f[0>n?~n:n].forEach(function(n){n._||(n._=1,r.push(n))})})})}}),c.forEach(function(n){delete n._}),{type:"MultiPolygon",arcs:a.map(function(e){var o=[];if(e.forEach(function(n){n.forEach(function(n){n.forEach(function(n){f[0>n?~n:n].length<2&&o.push(n)})})}),o=t(r,o),(n=o.length)>1)for(var u,c=i(e[0][0]),a=0;n>a;++a)if(c===i(o[a])){u=o[0],o[0]=o[a],o[a]=u;break}return o})}}function o(n,t){return"GeometryCollection"===t.type?{type:"FeatureCollection",features:t.geometries.map(function(t){return i(n,t)})}:i(n,t)}function i(n,t){var r={type:"Feature",id:t.id,properties:t.properties||{},geometry:u(n,t)};return null==t.id&&delete r.id,r}function u(n,t){function r(n,t){t.length&&t.pop();for(var r,e=s[0>n?~n:n],o=0,i=e.length;i>o;++o)t.push(r=e[o].slice()),a(r,o);0>n&&f(t,i)}function e(n){return n=n.slice(),a(n,0),n}function o(n){for(var t=[],e=0,o=n.length;o>e;++e)r(n[e],t);return t.length<2&&t.push(t[0].slice()),t}function i(n){for(var t=o(n);t.length<4;)t.push(t[0].slice());return t}function u(n){return n.map(i)}function c(n){var t=n.type;return"GeometryCollection"===t?{type:t,geometries:n.geometries.map(c)}:t in l?{type:t,coordinates:l[t](n)}:null}var a=v(n.transform),s=n.arcs,l={Point:function(n){return e(n.coordinates)},MultiPoint:function(n){return n.coordinates.map(e)},LineString:function(n){return o(n.arcs)},MultiLineString:function(n){return n.arcs.map(o)},Polygon:function(n){return u(n.arcs)},MultiPolygon:function(n){return n.arcs.map(u)}};return c(t)}function f(n,t){for(var r,e=n.length,o=e-t;o<--e;)r=n[o],n[o++]=n[e],n[e]=r}function c(n,t){for(var r=0,e=n.length;e>r;){var o=r+e>>>1;n[o]<t?r=o+1:e=o}return r}function a(n){function t(n,t){n.forEach(function(n){0>n&&(n=~n);var r=o[n];r?r.push(t):o[n]=[t]})}function r(n,r){n.forEach(function(n){t(n,r)})}function e(n,t){"GeometryCollection"===n.type?n.geometries.forEach(function(n){e(n,t)}):n.type in u&&u[n.type](n.arcs,t)}var o={},i=n.map(function(){return[]}),u={LineString:t,MultiLineString:r,Polygon:r,MultiPolygon:function(n,t){n.forEach(function(n){r(n,t)})}};n.forEach(e);for(var f in o)for(var a=o[f],s=a.length,l=0;s>l;++l)for(var h=l+1;s>h;++h){var p,g=a[l],v=a[h];(p=i[g])[f=c(p,v)]!==v&&p.splice(f,0,v),(p=i[v])[f=c(p,g)]!==g&&p.splice(f,0,g)}return i}function s(n,t){function r(n){i.remove(n),n[1][2]=t(n),i.push(n)}var e=v(n.transform),o=m(n.transform),i=g();return t||(t=h),n.arcs.forEach(function(n){for(var u,f,c=[],a=0,s=0,l=n.length;l>s;++s)f=n[s],e(n[s]=[f[0],f[1],1/0],s);for(var s=1,l=n.length-1;l>s;++s)u=n.slice(s-1,s+2),u[1][2]=t(u),c.push(u),i.push(u);for(var s=0,l=c.length;l>s;++s)u=c[s],u.previous=c[s-1],u.next=c[s+1];for(;u=i.pop();){var h=u.previous,p=u.next;u[1][2]<a?u[1][2]=a:a=u[1][2],h&&(h.next=p,h[2]=u[2],r(h)),p&&(p.previous=h,p[0]=u[0],r(p))}n.forEach(o)}),n}function l(n){for(var t,r=-1,e=n.length,o=n[e-1],i=0;++r<e;)t=o,o=n[r],i+=t[0]*o[1]-t[1]*o[0];return.5*i}function h(n){var t=n[0],r=n[1],e=n[2];return Math.abs((t[0]-e[0])*(r[1]-t[1])-(t[0]-r[0])*(e[1]-t[1]))}function p(n,t){return n[1][2]-t[1][2]}function g(){function n(n,t){for(;t>0;){var r=(t+1>>1)-1,o=e[r];if(p(n,o)>=0)break;e[o._=t]=o,e[n._=t=r]=n}}function t(n,t){for(;;){var r=t+1<<1,i=r-1,u=t,f=e[u];if(o>i&&p(e[i],f)<0&&(f=e[u=i]),o>r&&p(e[r],f)<0&&(f=e[u=r]),u===t)break;e[f._=t]=f,e[n._=t=u]=n}}var r={},e=[],o=0;return r.push=function(t){return n(e[t._=o]=t,o++),o},r.pop=function(){if(!(0>=o)){var n,r=e[0];return--o>0&&(n=e[o],t(e[n._=0]=n,0)),r}},r.remove=function(r){var i,u=r._;if(e[u]===r)return u!==--o&&(i=e[o],(p(i,r)<0?n:t)(e[i._=u]=i,u)),u},r}function v(n){if(!n)return y;var t,r,e=n.scale[0],o=n.scale[1],i=n.translate[0],u=n.translate[1];return function(n,f){f||(t=r=0),n[0]=(t+=n[0])*e+i,n[1]=(r+=n[1])*o+u}}function m(n){if(!n)return y;var t,r,e=n.scale[0],o=n.scale[1],i=n.translate[0],u=n.translate[1];return function(n,f){f||(t=r=0);var c=(n[0]-i)/e|0,a=(n[1]-u)/o|0;n[0]=c-t,n[1]=a-r,t=c,r=a}}function y(){}var d={version:"1.6.18",mesh:function(n){return u(n,r.apply(this,arguments))},meshArcs:r,merge:function(n){return u(n,e.apply(this,arguments))},mergeArcs:e,feature:o,neighbors:a,presimplify:s};"function"==typeof define&&define.amd?define(d):"object"==typeof module&&module.exports?module.exports=d:this.topojson=d}();
...\ No newline at end of file ...\ No newline at end of file
...@@ -24,6 +24,12 @@ ...@@ -24,6 +24,12 @@
24 opacity: 0.5; 24 opacity: 0.5;
25 } 25 }
26 26
27 +#topo #map {
28 + stroke-width: 2px;
29 + stroke: #aaaaaa;
30 + fill: transparent;
31 +}
32 +
27 33
28 /* NODES */ 34 /* NODES */
29 35
......
...@@ -106,7 +106,9 @@ ...@@ -106,7 +106,9 @@
106 config.force.pad + ',' + 106 config.force.pad + ',' +
107 config.force.pad + ')'; 107 config.force.pad + ')';
108 } 108 }
109 - } 109 + },
110 + // see below in creation of viewBox on main svg
111 + logicalSize: 1000
110 }; 112 };
111 113
112 // radio buttons 114 // radio buttons
...@@ -174,6 +176,9 @@ ...@@ -174,6 +176,9 @@
174 link, 176 link,
175 mask; 177 mask;
176 178
179 + // the projection for the map background
180 + var geoMapProjection;
181 +
177 // ============================== 182 // ==============================
178 // For Debugging / Development 183 // For Debugging / Development
179 184
...@@ -763,6 +768,15 @@ ...@@ -763,6 +768,15 @@
763 return; 768 return;
764 } 769 }
765 770
771 + var location = node.location;
772 + if (location && location.type === 'latlng') {
773 + var coord = geoMapProjection([location.lng, location.lat]);
774 + node.fixed = true;
775 + node.x = coord[0];
776 + node.y = coord[1];
777 + return;
778 + }
779 +
766 // Note: Placing incoming unpinned nodes at exactly the same point 780 // Note: Placing incoming unpinned nodes at exactly the same point
767 // (center of the view) causes them to explode outwards when 781 // (center of the view) causes them to explode outwards when
768 // the force layout kicks in. So, we spread them out a bit 782 // the force layout kicks in. So, we spread them out a bit
...@@ -1356,7 +1370,8 @@ ...@@ -1356,7 +1370,8 @@
1356 //trace = onos.exported.webSockTrace; 1370 //trace = onos.exported.webSockTrace;
1357 1371
1358 // NOTE: view.$div is a D3 selection of the view's div 1372 // NOTE: view.$div is a D3 selection of the view's div
1359 - svg = view.$div.append('svg'); 1373 + var viewBox = '0 0 ' + config.logicalSize + ' ' + config.logicalSize;
1374 + svg = view.$div.append('svg').attr('viewBox', viewBox);
1360 setSize(svg, view); 1375 setSize(svg, view);
1361 1376
1362 // add blue glow filter to svg layer 1377 // add blue glow filter to svg layer
...@@ -1452,7 +1467,7 @@ ...@@ -1452,7 +1467,7 @@
1452 } 1467 }
1453 1468
1454 // TODO: move these to config/state portion of script 1469 // TODO: move these to config/state portion of script
1455 - var geoJsonUrl = 'geoUsa.json', // TODO: Paul 1470 + var geoJsonUrl = 'json/map/continental_us.json', // TODO: Paul
1456 geoJson; 1471 geoJson;
1457 1472
1458 function loadGeoJsonData() { 1473 function loadGeoJsonData() {
...@@ -1496,29 +1511,38 @@ ...@@ -1496,29 +1511,38 @@
1496 1511
1497 function loadGeoMap() { 1512 function loadGeoMap() {
1498 fnTrace('loadGeoMap', geoJsonUrl); 1513 fnTrace('loadGeoMap', geoJsonUrl);
1499 - var w = network.view.width(),
1500 - h = network.view.height();
1501 1514
1502 - // TODO: load map layer from GeoJSON stored in 'geoJson' var... 1515 + // extracts the topojson data into geocoordinate-based geometry
1503 - // bgImg = svg.insert('<svg-element-type>', '#topo-G') ... 1516 + var topoData = topojson.feature(geoJson, geoJson.objects.states);
1504 1517
1505 - // TODO: Paul 1518 + // see: http://bl.ocks.org/mbostock/4707858
1506 - } 1519 + geoMapProjection = d3.geo.mercator();
1520 + var path = d3.geo.path().projection(geoMapProjection);
1507 1521
1508 - function resizeBg(view) { 1522 + geoMapProjection
1509 - if (geoJson) { 1523 + .scale(1)
1510 - // TODO : resize GeoJSON map 1524 + .translate([0, 0]);
1511 1525
1512 - // TODO: Paul 1526 + // [[x1,y1],[x2,y2]]
1527 + var b = path.bounds(topoData);
1528 + // TODO: why 1.75?
1529 + var s = 1.75 / Math.max((b[1][0] - b[0][0]) / config.logicalSize, (b[1][1] - b[0][1]) / config.logicalSize);
1530 + var t = [(config.logicalSize - s * (b[1][0] + b[0][0])) / 2, (config.logicalSize - s * (b[1][1] + b[0][1])) / 2];
1513 1531
1514 - } else if (bgImg) { 1532 + geoMapProjection
1515 - setSize(bgImg, view); 1533 + .scale(s)
1516 - } 1534 + .translate(t);
1535 +
1536 + bgImg = svg.insert("g", '#topo-G');
1537 + bgImg.attr('id', 'map').selectAll('path')
1538 + .data(topoData.features)
1539 + .enter()
1540 + .append('path')
1541 + .attr('d', path);
1517 } 1542 }
1518 1543
1519 function resize(view, ctx, flags) { 1544 function resize(view, ctx, flags) {
1520 setSize(svg, view); 1545 setSize(svg, view);
1521 - resizeBg(view);
1522 1546
1523 // TODO: hook to recompute layout, perhaps? work with zoom/pan code 1547 // TODO: hook to recompute layout, perhaps? work with zoom/pan code
1524 // adjust force layout size 1548 // adjust force layout size
......