Source: Map/gmlToGeoJson.js

'use strict';

/*global require*/
var defined = require('terriajs-cesium/Source/Core/defined');
var RuntimeError = require('terriajs-cesium/Source/Core/RuntimeError');

var gmlNamespace = 'http://www.opengis.net/gml';

/**
 * Converts a GML v3.1.1 simple features document to GeoJSON.
 * @param  {Document|String} xml The GML document.
 * @return {Object} The GeoJSON object.
 */
function gmlToGeoJson(xml) {
    if (typeof xml === 'string') {
        var parser = new DOMParser();
        xml = parser.parseFromString(xml, 'text/xml');
    }

    var result = [];

    var featureCollection = xml.documentElement;

    var featureMembers = featureCollection.getElementsByTagNameNS(gmlNamespace, 'featureMember');
    for (var featureIndex = 0; featureIndex < featureMembers.length; ++featureIndex) {
        var featureMember = featureMembers[featureIndex];

        var properties = {};

        getGmlPropertiesRecursively(featureMember, properties);

        var feature = {
            type: 'Feature',
            geometry: getGmlGeometry(featureMember),
            properties: properties
        };

        if (defined(feature.geometry)) {
            result.push(feature);
        }
    }

    return {
        type: 'FeatureCollection',
        crs: { type: 'EPSG', properties: { code: '4326' } },
        features: result
    };
}

var gmlSimpleFeatureNames = [
    'Curve',
    'LineString',
    'Point',
    'Polygon',
    'MultiCurve',
    'MultPoint',
    'MultiSurface',
    'Surface'
];

function getGmlPropertiesRecursively(gmlNode, properties) {
    var isSingleValue = true;

    for (var i = 0; i < gmlNode.childNodes.length; ++i) {
        var child = gmlNode.childNodes[i];

        if (child.nodeType === Node.ELEMENT_NODE) {
            isSingleValue = false;
        }

        if (gmlSimpleFeatureNames.indexOf(child.localName) >= 0) {
            continue;
        }

        if (child.hasChildNodes() && getGmlPropertiesRecursively(child, properties)) {
            properties[child.localName] = child.textContent;
        }
    }

    return isSingleValue;
}

function getGmlGeometry(gmlNode) {
    var result;

    for (var i = 0; !defined(result) && i < gmlNode.childNodes.length; ++i) {
        var child = gmlNode.childNodes[i];

        if (gmlSimpleFeatureNames.indexOf(child.localName) >= 0) {
            return createGeoJsonGeometryFeatureFromGmlGeometry(child);
        } else {
            result = getGmlGeometry(child);
        }
    }

    return result;
}

function createLineStringFromGmlGeometry(geometry) {
    var coordinates = [];

    var posNodes = geometry.getElementsByTagNameNS(gmlNamespace, 'posList');
    for (var i = 0; i < posNodes.length; ++i) {
        var positions = gml2coord(posNodes[i].textContent);
        for (var j = 0; j < positions.length; ++j) {
            coordinates.push(positions[j]);
        }
    }

    return {
        type: 'LineString',
        coordinates: coordinates
    };
}

function createPointFromGmlGeometry(geometry) {
    var posNodes = geometry.getElementsByTagNameNS(gmlNamespace, 'pos');
    if (posNodes < 1) {
        throw new RuntimeError('GML point element is missing a pos element.');
    }

    return {
        type: 'Point',
        coordinates: gml2coord(posNodes[0].textContent)[0]
    };
}

function createMultiLineStringFromGmlGeometry(geometry) {
    var curveMembers = geometry.getElementsByTagNameNS(gmlNamespace, 'posList');
    var curves = [];
    for (var i = 0; i < curveMembers.length; ++i) {
        curves.push(gml2coord(curveMembers[i].textContent));
    }

    return {
        type: 'MultiLineString',
        coordinates: curves
    };
}

function createMultiPolygonFromGmlGeomtry(geometry) {
    var coordinates = [];

    var polygons = geometry.getElementsByTagNameNS(gmlNamespace, 'Polygon');
    for (var i = 0; i < polygons.length; ++i) {
        var polygon = polygons[i];
        var exteriorNodes = polygon.getElementsByTagNameNS(gmlNamespace, 'exterior');
        if (exteriorNodes.length < 1) {
            throw new RuntimeError('GML polygon is missing its exterior ring.');
        }

        var polygonCoordinates = [];

        var exterior = exteriorNodes[0];
        var posListNodes = exterior.getElementsByTagNameNS(gmlNamespace, 'posList');
        if (posListNodes.length < 1) {
            throw new RuntimeError('GML polygon\'s exterior ring is missing a posList.');
        }

        polygonCoordinates.push(gml2coord(posListNodes[0].textContent));

        var interiors = polygon.getElementsByTagNameNS(gmlNamespace, 'interior');
        for (var j = 0; j < interiors.length; ++j) {
            var interior = interiors[j];
            var interiorPosListNodes = interior.getElementsByTagNameNS(gmlNamespace, 'posList');
            if (interiorPosListNodes.length < 1) {
                continue;
            }

            polygonCoordinates.push(gml2coord(interiorPosListNodes[0].textContent));
        }

        coordinates.push(polygonCoordinates);
    }

    return {
        type: 'MultiPolygon',
        coordinates: coordinates
    };
}

function createPolygonFromGmlGeometry(geometry) {
    var polygonCoordinates = [];

    var exteriorNodes = geometry.getElementsByTagNameNS(gmlNamespace, 'exterior');
    if (exteriorNodes.length < 1) {
        throw new RuntimeError('GML polygon is missing its exterior ring.');
    }

    var exterior = exteriorNodes[0];
    var posListNodes = exterior.getElementsByTagNameNS(gmlNamespace, 'posList');
    if (posListNodes.length < 1) {
        throw new RuntimeError('GML polygon\'s exterior ring is missing a posList.');
    }

    polygonCoordinates.push(gml2coord(posListNodes[0].textContent));

    var interiors = geometry.getElementsByTagNameNS(gmlNamespace, 'interior');
    for (var j = 0; j < interiors.length; ++j) {
        var interior = interiors[j];
        var interiorPosListNodes = interior.getElementsByTagNameNS(gmlNamespace, 'posList');
        if (interiorPosListNodes.length < 1) {
            continue;
        }

        polygonCoordinates.push(gml2coord(interiorPosListNodes[0].textContent));
    }

    return {
        type: 'Polygon',
        coordinates: polygonCoordinates
    };
}

function createMultiPointFromGmlGeometry(geometry) {
    var posNodes = geometry.getElementsByTagNameNS(gmlNamespace, 'pos');

    var coordinates = [];

    for (var i = 0; i < posNodes.length; ++i) {
        coordinates.push(gml2coord(posNodes[i].textContent)[0]);
    }

    return {
        type: 'MultiPoint',
        coordinates: coordinates
    };
}

var featureCreators = {
    Curve: createLineStringFromGmlGeometry,
    LineString: createLineStringFromGmlGeometry,
    Point: createPointFromGmlGeometry,
    Polygon: createPolygonFromGmlGeometry,
    MultiCurve: createMultiLineStringFromGmlGeometry,
    MultPoint: createMultiPointFromGmlGeometry,
    MultiSurface: createMultiPolygonFromGmlGeomtry,
    Surface: createPolygonFromGmlGeometry

};

function createGeoJsonGeometryFeatureFromGmlGeometry(geometry) {
    var type = geometry.localName;
    var creator = featureCreators[type];
    if (!defined(creator)) {
        throw new RuntimeError('GML contains unsupported feature type: ' + type);
    }

    return creator(geometry);
}

function isNotEmpty(s) {
    return s.length !== 0;
}

//Utility function to change esri gml positions to geojson positions
function gml2coord(posList) {
    var pnts = posList.split(/[ ,]+/).filter(isNotEmpty);
    var coords = [];
    for (var i = 0; i < pnts.length; i+=2) {
        coords.push([parseFloat(pnts[i+1]), parseFloat(pnts[i])]);
    }
    return coords;
}

module.exports = gmlToGeoJson;