You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

531 lines
21 KiB

'use strict';
var NVG = class {
constructor(input) {
this.document = 'nvg';
this.items = [];
this.version = '2.0.2'
if (Array.isArray(input)){
this.items = input;
}
if (typeof input == 'object' && !Array.isArray(input)){
this.items.push(input);
}
if (typeof input == 'string'){
//do stuff with input object
try {
input = JSON.parse(input);
for (var key in input){
this[key] = input[key];
}
}catch (e) {
//So parse as JSON failed, try to parse it as xml
this.parseXML(input);
}
}
}
getItem(uri){ //returns item from uri
if(typeof uri == 'undefined')return this.items;
function getItemByURI(uri, items){
for (var i = 0; i< items.length; i++){
if(items[i].uri == uri) return items[i];
if(items[i].hasOwnProperty('items')){
var result = getItemByURI(uri, items[i].items);
if(result) return result;
}
}
return false;
}
return getItemByURI(uri, this.items);
}
getItems(){ //returns all items
return this.items;
}
parseXML(xml){
//parse XML string to JSON
function tagAttributes(nodes, current){
for (var i = 0; i < nodes.length; i++){
var node = nodes[i];
var nodeName = node.nodeName.split(':');
if(nodeName[0] == 'dc' || nodeName[0] == 'dcterms'){
nodeName = nodeName[0];
}else{
nodeName = nodeName[1];
}
if(node.nodeType == 1 && nodeName){
nodeName = nodeName.toLowerCase();
switch (nodeName) {
case 'begin':
current[nodeName] = node.textContent;
break;
case 'end':
current[nodeName] = node.textContent;
break;
case 'dc':
case 'dcterms':
current[node.nodeName] = node.textContent;
break;
case 'content':
current[nodeName] = node.textContent;
break;
case 'exclude':
if (!current.hasOwnProperty(nodeName)){
current.exclusion = [];
}
tagAttributes(node.childNodes, current.exclusion);
break;
case 'extendeddata':
if (!current.hasOwnProperty(nodeName)){
current[nodeName] = {};
current[nodeName].simpledata = [];
}
nodeAttibutes(node, current[nodeName]);
parseSubNodes(node.childNodes, current[nodeName]);
break;
case 'extension':
console.log('TODO tagAttributes: ' + nodeName);
// TODO How to handle extended data
current[nodeName] = [];//this is for node
break;
case 'metadata':
current[nodeName] = {};
tagAttributes(node.childNodes, current[nodeName]);
break;
case 'textinfo':
current[nodeName] = node.textContent;
break;
case 'timespan':
current[nodeName] = {};
tagAttributes(node.childNodes, current[nodeName]);
break;
case 'timestamp':
current[nodeName] = node.textContent;
break;
case 'simplefield':
if (!current.hasOwnProperty(nodeName)){
current[nodeName] = [];
}
var field = {};
nodeAttibutes(node, field);
current[nodeName].push(field);
break;
case 'arcband-ring':
case 'circular-ring':
case 'elliptic-ring':
case 'linear-ring':
case 'rect-ring':
var exclude = {};
exclude.ring = nodeName.replace('-','');
nodeAttibutes(node, exclude);
current.push(exclude);
break;
default:
//Debug logging, remove later
if(['arc','arcband','arrow','circle','composite','content-item','corridor','ellipse','g','multipoint','orbit','point','polygon','polyline','rect','text'].lastIndexOf(nodeName) == -1){
console.log('TODO tagAttributes default: ' + nodeName);
}
}
}
}
}
function nodeAttibutes(node, current){
Array.prototype.slice.call(node.attributes).forEach(function(attr) {
if (attr.name == 'modifiers' || attr.name == 'style') {
current[attr.name] = {};
var attr_list = attr.value.trim().split(';');
for (var j = 0; j < attr_list.length; j++){
if(attr_list[j]){
var s = attr_list[j].split(':');
if(s[0] && s[1])current[attr.name][s[0].trim()] = isNaN(Number(s[1].trim()))?s[1].trim():Number(s[1].trim());
}
}
return;
}
if (attr.name == 'points') {
current[attr.name] = [];
var attr_list = attr.value.trim().split(' ');
for (var j = 0; j < attr_list.length; j++){
if(attr_list[j]){
var s = attr_list[j].split(',');
if(s[0] && s[1])current[attr.name].push([Number(s[0]),Number(s[1])]);
}
}
return;
}
current[attr.name] = isNaN(Number(attr.value))?attr.value:Number(attr.value);
});
}
function parseSubNodes(nodes, current){
for (var i = 0; i < nodes.length; i++){
var node = nodes[i];
if(node.nodeType == 1){
var nodeName = node.nodeName.split(':')[1] || node.nodeName;
nodeName = nodeName.toLowerCase();
var item = {};
if (['extendeddata','extension','metadata','schema','section','simpledata','simplefield'].lastIndexOf(nodeName) != -1){
switch (nodeName) {
case 'extendeddata':
current[nodeName] = item;
nodeAttibutes(node, item);
tagAttributes(node.childNodes, item);
break;
case 'extension':
console.log('TODO parsesubnodes: ' + nodeName)
// TODO How to handle extended data
current[nodeName] = [];//this is for root level
break;
case 'metadata':
console.log('TODO parsesubnodes: ' + nodeName)
// TODO How to handle metadata data
current[nodeName] = item;
break;
case 'schema':
if (!current.hasOwnProperty(nodeName)){
current[nodeName] = [];
}
current[nodeName].push(item);
nodeAttibutes(node, item);
tagAttributes(node.childNodes, item);
break;
case 'section':
if (!current.hasOwnProperty('simpledatasection')){
current.simpledatasection = [];
}
current.simpledatasection.push(item);
nodeAttibutes(node, item);
item.simpledata = [];
parseSubNodes(node.childNodes, item);
break;
case 'simpledata':
nodeAttibutes(node, item);
item.value = node.textContent;
current.simpledata.push(item);
tagAttributes(node.childNodes, item);
break;
case 'simplefield':
current[nodeName] = item;
nodeAttibutes(node, item);
tagAttributes(node.childNodes, item);
break;
default:
console.log('TODO parsesubnodes default: ' + nodeName)
}
}else{ //This is all drawables
nodeAttibutes(node, item);
item.drawable = nodeName;
if(node.childNodes.length){
tagAttributes(node.childNodes, item);
}
if(item.drawable == 'g' || item.drawable == 'composite'){
item.items = [];
parseSubNodes(node.childNodes, item);
}
if(item.drawable == 'a'){ //This is for handling the old A element
parseSubNodes(node.childNodes, current);
}else{ // otherwise just add featuers
current.items.push(item);
}
}
}
}
}
var xml = (new DOMParser()).parseFromString(xml , "text/xml");
if(xml.firstChild.nodeName == 'nvg' || xml.firstChild.nodeName.split(':')[1] == 'nvg'){//check that we actually are parsing NVG but ignore namespace
this.version = xml.firstChild.getAttribute('version');
this.items = [];
var nodes = xml.firstChild.childNodes;
parseSubNodes(nodes, this);
}
}
toGeoJSON(){
function bearing(p1,p2){
var l1 = p1[0] * (Math.PI/180);
var l2 = p2[0] * (Math.PI/180);
var f1 = p1[1] * (Math.PI/180);
var f2 = p2[1] * (Math.PI/180);
var y = Math.sin(l2-l1)*Math.cos(f2);
var x = Math.cos(f1)*Math.sin(f2)-Math.sin(f1)*Math.cos(f2)*Math.cos(l2-l1);
return Math.atan2(y,x)/(Math.PI/180);
}
function distBearing(point, dist, bearing){
var angularDist = dist/6371e3;
var bearing = bearing * (Math.PI/180);
var lng = point[0] * (Math.PI/180);
var lat = point[1] * (Math.PI/180);
var lat2 = Math.asin(Math.sin(lat)*Math.cos(angularDist)+Math.cos(lat)*Math.sin(angularDist)*Math.cos(bearing));
var lng2 = (lng+Math.atan2(Math.sin(bearing)*Math.sin(angularDist)*Math.cos(lat),Math.cos(angularDist)-Math.sin(lat)*Math.sin(lat2)));
lat2 = lat2/(Math.PI/180);
lng2 = ((lng2/(Math.PI/180))+540)%360-180;
return [lng2,lat2];
}
function exclusions(exclusion){
var exclude = [];
switch (exclusion.ring) {
case 'arcbandring':
var startangle = exclusion.startangle;
var endangle = exclusion.endangle;
if(startangle > endangle) endangle += 360;
for (var j = startangle; j <= endangle; j+=2){
exclude.push(distBearing([exclusion.cx,exclusion.cy], exclusion.minr, j));
}
for (var j = endangle; j >= startangle; j-=2){
exclude.push(distBearing([exclusion.cx,exclusion.cy], exclusion.maxr, j));
}
exclude.push(distBearing([exclusion.cx,exclusion.cy], exclusion.minr, startangle));
break;
case 'ellipticalring':
for (var j = 360; j >= 0; j-=2){
var radius = exclusion.ry * exclusion.rx / Math.sqrt(Math.pow(exclusion.rx * Math.cos(j * (Math.PI/180)),2) + Math.pow(exclusion.ry * Math.sin(j * (Math.PI/180)),2));
exclude.push(distBearing([exclusion.cx,exclusion.cy], radius, j-(exclusion.rotation||0)));
}
break;
case 'linearring':
exclude = exclusion.points;
exclude.push(exclusion.points[0]);
break;
case 'rectangularring':
var diagonalRadius = Math.sqrt(Math.pow(exclusion.rx,2)+Math.pow(exclusion.rx,2));
var angle;
angle = ((Math.PI/2)-Math.atan2(exclusion.ry, exclusion.rx)) / (Math.PI/180);
exclude.push(distBearing([exclusion.cx,exclusion.cy], diagonalRadius, exclusion.rotation?angle-exclusion.rotation:angle));
angle = ((Math.PI/2)-Math.atan2(-exclusion.ry, exclusion.rx)) / (Math.PI/180);
exclude.push(distBearing([exclusion.cx,exclusion.cy], diagonalRadius, exclusion.rotation?angle-exclusion.rotation:angle));
angle = ((Math.PI/2)-Math.atan2(-exclusion.ry, -exclusion.rx)) / (Math.PI/180);
exclude.push(distBearing([exclusion.cx,exclusion.cy], diagonalRadius, exclusion.rotation?angle-exclusion.rotation:angle));
angle = ((Math.PI/2)-Math.atan2(exclusion.ry, -exclusion.rx)) / (Math.PI/180);
exclude.push(distBearing([exclusion.cx,exclusion.cy], diagonalRadius, exclusion.rotation?angle-exclusion.rotation:angle));
angle = ((Math.PI/2)-Math.atan2(exclusion.ry, exclusion.rx)) / (Math.PI/180);
exclude.push(distBearing([exclusion.cx,exclusion.cy], diagonalRadius, exclusion.rotation?angle-exclusion.rotation:angle));
break;
default:
console.log('TODO parse item default: ' + exclusion.ring)
}
return exclude;
}
function items2features(items, geometrycollection){
var features = [];
for (var i = 0; i < items.length; i++){
var item = items[i];
var feature = { "type": "Feature", "properties" : {}};
for (var key in item){
if(key == 'uri'){
feature.id = item.uri;
}else{
feature.properties[key] = item[key];
}
}
switch (item.drawable) {
case 'arc':
feature.geometry = {"type": "LineString"};
feature.geometry.coordinates = [];
var startangle = item.startangle;
var endangle = item.endangle;
if(startangle > endangle) endangle += 360;
for (var j = startangle; j <= endangle; j+=2){
var radius = item.ry * item.rx / Math.sqrt(Math.pow(item.rx * Math.cos(j * (Math.PI/180)),2) + Math.pow(item.ry * Math.sin(j * (Math.PI/180)),2));
feature.geometry.coordinates.push(distBearing([item.cx,item.cy], radius, item.rotation?j-item.rotation:j));
}
break;
case 'arcband':
feature.geometry = {"type": "Polygon"};
feature.geometry.coordinates = [[]];
var startangle = item.startangle;
var endangle = item.endangle;
if(startangle > endangle) endangle += 360;
for (var j = startangle; j <= endangle; j+=2){
feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], item.minr, j));
}
for (var j = endangle; j >= startangle; j-=2){
feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], item.maxr, j));
}
feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], item.minr, startangle));
break;
case 'arrow':
var direction;
feature.geometry = {"type": "LineString"};
feature.geometry.coordinates = [];
direction = (bearing(item.points[0],item.points[1]) +360) % 360;
feature.geometry.coordinates.push(distBearing(item.points[0], item.width/2, direction-90));
for (var j = 1; j < item.points.length-1; j++){
var direction1 = (bearing(item.points[j], item.points[j-1]) +360) % 360;
var direction2 = (bearing(item.points[j], item.points[j+1]) +360) % 360;
var factor = 1/Math.sin(((direction2-direction1)/2)*(Math.PI/180));
feature.geometry.coordinates.push(distBearing(item.points[j], (item.width/2)*factor , ((direction1+direction2)/2)));
}
direction = (bearing(item.points[item.points.length-1],item.points[item.points.length-2]) + 180) % 360;
//Arrowhead
var point = distBearing(item.points[item.points.length-1], item.width, direction+180);
feature.geometry.coordinates.push(distBearing(point, item.width/2, direction-90))
feature.geometry.coordinates.push(distBearing(point, item.width, direction-90))
feature.geometry.coordinates.push(item.points[item.points.length-1]);
feature.geometry.coordinates.push(distBearing(point, item.width, direction+90))
feature.geometry.coordinates.push(distBearing(point, item.width/2, direction+90))
for (var j = item.points.length-2; j > 0; j--){
var direction1 = (bearing(item.points[j], item.points[j-1]) +360) % 360;
var direction2 = (bearing(item.points[j], item.points[j+1]) +360) % 360;
var factor = 1/Math.sin(((direction2-direction1)/2)*(Math.PI/180));
feature.geometry.coordinates.push(distBearing(item.points[j], -(item.width/2)*factor, ((direction1+direction2)/2)));
}
direction = (bearing(item.points[0],item.points[1]) +360) % 360;
feature.geometry.coordinates.push(distBearing(item.points[0], item.width/2, direction+90));
break;
case 'circle':
feature.geometry = {"type": "Polygon"};
feature.geometry.coordinates = [[]];
for (var j = 360; j >= 0; j-=5){
feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], item.r, j));
}
break;
case 'composite':
//Flatten composites at the moment
var subfeatures = items2features(item.items, true);
for (key in subfeatures){
subfeatures[key].properties.parent = {};//feature.properties;
if(item.uri)subfeatures[key].properties.parent.uri = item.uri;
features.push(subfeatures[key]);
}
break;
case 'corridor':
var direction;
feature.geometry = {"type": "Polygon"};
feature.geometry.coordinates = [[]];
direction = (bearing(item.points[0],item.points[1]) +360) % 360;
feature.geometry.coordinates[0].push(distBearing(item.points[0], item.width/2, direction-90));
for (var j = 1; j < item.points.length-1; j++){
var direction1 = (bearing(item.points[j], item.points[j-1]) +360) % 360;
var direction2 = (bearing(item.points[j], item.points[j+1]) +360) % 360;
var factor = 1/Math.sin(((direction2-direction1)/2)*(Math.PI/180));
feature.geometry.coordinates[0].push(distBearing(item.points[j], (item.width/2)*factor, ((direction1+direction2)/2)));
}
direction = (bearing(item.points[item.points.length-1],item.points[item.points.length-2]) + 180) % 360;
feature.geometry.coordinates[0].push(distBearing(item.points[item.points.length-1], item.width/2, direction-90));
feature.geometry.coordinates[0].push(distBearing(item.points[item.points.length-1], item.width/2, direction+90));
for (var j = item.points.length-2; j > 0; j--){
var direction1 = (bearing(item.points[j], item.points[j-1]) +360) % 360;
var direction2 = (bearing(item.points[j], item.points[j+1]) +360) % 360;
var factor = 1/Math.sin(((direction2-direction1)/2)*(Math.PI/180));
feature.geometry.coordinates[0].push(distBearing(item.points[j], -(item.width/2)*factor, ((direction1+direction2)/2)));
}
direction = (bearing(item.points[0],item.points[1]) +360) % 360;
feature.geometry.coordinates[0].push(distBearing(item.points[0], item.width/2, direction+90));
feature.geometry.coordinates[0].push(distBearing(item.points[0], item.width/2, direction-90));//Close line
break;
case 'ellipse':
feature.geometry = {"type": "Polygon"};
feature.geometry.coordinates = [[]];
for (var j = 360; j >= 0; j-=2){
var radius = item.ry * item.rx / Math.sqrt(Math.pow(item.rx * Math.cos(j * (Math.PI/180)),2) + Math.pow(item.ry * Math.sin(j * (Math.PI/180)),2));
feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], radius, j-(item.rotation||0)));
}
break;
case 'g':
//Flatten groups
var subfeatures = items2features(item.items, true);
for (key in subfeatures){
subfeatures[key].properties.parent = {};//feature.properties;
if(item.uri)subfeatures[key].properties.parent.uri = item.uri;
features.push(subfeatures[key]);
}
break;
case 'multipoint':
feature.geometry = {"type": "MultiPoint"};
feature.geometry.coordinates = item.points;
delete feature.properties.points;
break;
case 'orbit':
var direction;
feature.geometry = {"type": "Polygon"};
feature.geometry.coordinates = [[]];
direction = (Math.atan2(item.points[1][0] - item.points[0][0], item.points[1][1] - item.points[0][1]) - (Math.PI/2)) / (Math.PI/180);
feature.geometry.coordinates[0].push(distBearing(item.points[0], item.width/2, direction));
for (var j = 0; j <= 180; j+=2){
feature.geometry.coordinates[0].push(distBearing(item.points[1], item.width/2, direction + j ));
}
direction = (Math.atan2(item.points[0][0] - item.points[1][0], item.points[0][1] - item.points[1][1]) - (Math.PI/2)) / (Math.PI/180);
feature.geometry.coordinates[0].push(distBearing(item.points[item.points.length-1], item.width/2, direction));
for (var j = 0; j <= 180; j+=2){
feature.geometry.coordinates[0].push(distBearing(item.points[0], item.width/2, direction + j ));
}
direction = (Math.atan2(item.points[1][0] - item.points[0][0], item.points[1][1] - item.points[0][1]) - (Math.PI/2)) / (Math.PI/180);
feature.geometry.coordinates[0].push(distBearing(item.points[0], item.width/2, direction));
break;
break;
case 'point':
feature.geometry = {"type": "Point"};
feature.geometry.coordinates = [item.x, item.y];
break;
case 'polygon':
feature.geometry = {"type": "Polygon"};
feature.geometry.coordinates = [item.points];
feature.geometry.coordinates[0].push(item.points[0])
delete feature.properties.points;
break;
case 'polyline':
feature.geometry = {"type": "LineString"};
feature.geometry.coordinates = item.points;
delete feature.properties.points;
break;
case 'rect':
feature.geometry = {"type": "Polygon"};
feature.geometry.coordinates = [[]];
var diagonalRadius = Math.sqrt(Math.pow(item.rx,2)+Math.pow(item.rx,2));
var angle;
angle = ((Math.PI/2)-Math.atan2(item.ry, item.rx)) / (Math.PI/180);
feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], diagonalRadius, item.rotation?angle-item.rotation:angle));
angle = ((Math.PI/2)-Math.atan2(-item.ry, item.rx)) / (Math.PI/180);
feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], diagonalRadius, item.rotation?angle-item.rotation:angle));
angle = ((Math.PI/2)-Math.atan2(-item.ry, -item.rx)) / (Math.PI/180);
feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], diagonalRadius, item.rotation?angle-item.rotation:angle));
angle = ((Math.PI/2)-Math.atan2(item.ry, -item.rx)) / (Math.PI/180);
feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], diagonalRadius, item.rotation?angle-item.rotation:angle));
angle = ((Math.PI/2)-Math.atan2(item.ry, item.rx)) / (Math.PI/180);
feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], diagonalRadius, item.rotation?angle-item.rotation:angle));
break;
case 'text':
feature.geometry = {"type": "Point"};
feature.geometry.coordinates = [item.x, item.y];
break;
default:
console.log('TODO parse item default: ' + item.drawable)
}
if(item.hasOwnProperty('exclusion')){
for (var e = 0; e < item.exclusion.length; e++){
feature.geometry.coordinates.push(exclusions(item.exclusion[e]));
}
}
if(feature.geometry){
features.push(feature);
}
}
return features;
}
var geoJSON = {};
geoJSON.type = 'FeatureCollection';
for (var key in this){
if(key == 'items'){
geoJSON.features = items2features(this.items, false);
}else{
geoJSON[key] = this[key];
}
}
return geoJSON;
}
toXML(){
//parse this to NVG XML
}
};