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
Showing
8 changed files
with
72 additions
and
27 deletions
... | @@ -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 | } | ... | ... |
web/gui/src/main/webapp/json/map/README.txt
0 → 100644
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 |
... | @@ -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 | ... | ... |
-
Please register or login to post a comment