1 Star 0 Fork 8

lujie/KML转geojson

forked from 阿夜/KML转geojson 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
togeojson.js 22.82 KB
一键复制 编辑 原始数据 按行查看 历史
阿夜 提交于 2020-11-25 11:15 . cs
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
// cast array x into numbers
// get the content of a text node, if any
function nodeVal(x) {
if (x && x.normalize) {
x.normalize();
}
return x && x.textContent || "";
}
// one Y child of X, if any, otherwise null
function get1(x, y) {
const n = x.getElementsByTagName(y);
return n.length ? n[0] : null;
}
function getLineStyle(extensions) {
const style = {};
if (extensions) {
const lineStyle = get1(extensions, "line");
if (lineStyle) {
const color = nodeVal(get1(lineStyle, "color")),
opacity = parseFloat(nodeVal(get1(lineStyle, "opacity"))),
width = parseFloat(nodeVal(get1(lineStyle, "width")));
if (color) style.stroke = color;
if (!isNaN(opacity)) style["stroke-opacity"] = opacity;
// GPX width is in mm, convert to px with 96 px per inch
if (!isNaN(width)) style["stroke-width"] = width * 96 / 25.4;
}
}
return style;
}
// get the contents of multiple text nodes, if present
function getMulti(x, ys) {
const o = {};
let n;
let k;
for (k = 0; k < ys.length; k++) {
n = get1(x, ys[k]);
if (n) o[ys[k]] = nodeVal(n);
}
return o;
}
function getProperties(node) {
const prop = getMulti(node, [
"name",
"cmt",
"desc",
"type",
"time",
"keywords"]);
// Parse additional data from our Garmin extension(s)
const extensions = node.getElementsByTagNameNS(
"http://www.garmin.com/xmlschemas/GpxExtensions/v3",
"*");
for (let i = 0; i < extensions.length; i++) {
const extension = extensions[i];
// Ignore nested extensions, like those on routepoints or trackpoints
if (extension.parentNode.parentNode === node) {
prop[extension.tagName.replace(":", "_")] = nodeVal(extension);
}
}
const links = node.getElementsByTagName("link");
if (links.length) prop.links = [];
for (let i = 0; i < links.length; i++) {
prop.links.push(
Object.assign(
{ href: links[i].getAttribute("href") },
getMulti(links[i], ["text", "type"])));
}
return prop;
}
function coordPair(x) {
const ll = [
parseFloat(x.getAttribute("lon")),
parseFloat(x.getAttribute("lat"))];
const ele = get1(x, "ele");
// handle namespaced attribute in browser
const heartRate = get1(x, "gpxtpx:hr") || get1(x, "hr");
const time = get1(x, "time");
let e;
if (ele) {
e = parseFloat(nodeVal(ele));
if (!isNaN(e)) {
ll.push(e);
}
}
const result = {
coordinates: ll,
time: time ? nodeVal(time) : null,
extendedValues: [] };
if (heartRate) {
result.extendedValues.push(["heartRate", parseFloat(nodeVal(heartRate))]);
}
const extensions = get1(x, "extensions");
if (extensions !== null) {
for (const name of ["speed", "course", "hAcc", "vAcc"]) {
const v = parseFloat(nodeVal(get1(extensions, name)));
if (!isNaN(v)) {
result.extendedValues.push([name, v]);
}
}
}
return result;
}
function getRoute(node) {
const line = getPoints(node, "rtept");
if (!line) return;
return {
type: "Feature",
properties: Object.assign(
getProperties(node),
getLineStyle(get1(node, "extensions")),
{ _gpxType: "rte" }),
geometry: {
type: "LineString",
coordinates: line.line } };
}
function getPoints(node, pointname) {
const pts = node.getElementsByTagName(pointname);
if (pts.length < 2) return; // Invalid line in GeoJSON
const line = [];
const times = [];
const extendedValues = {};
for (let i = 0; i < pts.length; i++) {
const c = coordPair(pts[i]);
line.push(c.coordinates);
if (c.time) times.push(c.time);
for (let j = 0; j < c.extendedValues.length; j++) {
const [name, val] = c.extendedValues[j];
const plural = name + "s";
if (!extendedValues[plural]) {
extendedValues[plural] = Array(pts.length).fill(null);
}
extendedValues[plural][i] = val;
}
}
return {
line: line,
times: times,
extendedValues: extendedValues };
}
function getTrack(node) {
const segments = node.getElementsByTagName("trkseg");
const track = [];
const times = [];
const extractedLines = [];
for (let i = 0; i < segments.length; i++) {
const line = getPoints(segments[i], "trkpt");
if (line) {
extractedLines.push(line);
if (line.times && line.times.length) times.push(line.times);
}
}
if (extractedLines.length === 0) return;
const multi = extractedLines.length > 1;
const properties = Object.assign(
getProperties(node),
getLineStyle(get1(node, "extensions")),
{ _gpxType: "trk" },
times.length ?
{
coordTimes: multi ? times : times[0] } :
{});
for (let i = 0; i < extractedLines.length; i++) {
const line = extractedLines[i];
track.push(line.line);
for (const [name, val] of Object.entries(line.extendedValues)) {
if (multi) {
if (!properties[name])
properties[name] = extractedLines.map((line) =>
new Array(line.line.length).fill(null));
properties[name][i] = val;
} else {
properties[name] = val;
}
}
}
return {
type: "Feature",
properties: properties,
geometry: multi ?
{
type: "MultiLineString",
coordinates: track } :
{
type: "LineString",
coordinates: track[0] } };
}
function getPoint(node) {
return {
type: "Feature",
properties: Object.assign(getProperties(node), getMulti(node, ["sym"])),
geometry: {
type: "Point",
coordinates: coordPair(node).coordinates } };
}
function* gpxGen(doc) {
const tracks = doc.getElementsByTagName("trk");
const routes = doc.getElementsByTagName("rte");
const waypoints = doc.getElementsByTagName("wpt");
for (let i = 0; i < tracks.length; i++) {
const feature = getTrack(tracks[i]);
if (feature) yield feature;
}
for (let i = 0; i < routes.length; i++) {
const feature = getRoute(routes[i]);
if (feature) yield feature;
}
for (let i = 0; i < waypoints.length; i++) {
yield getPoint(waypoints[i]);
}
}
function gpx(doc) {
return {
type: "FeatureCollection",
features: Array.from(gpxGen(doc)) };
}
const EXTENSIONS_NS = "http://www.garmin.com/xmlschemas/ActivityExtension/v2";
const TRACKPOINT_ATTRIBUTES = [
["heartRate", "heartRates"],
["Cadence", "cadences"],
// Extended Trackpoint attributes
["Speed", "speeds"],
["Watts", "watts"]];
const LAP_ATTRIBUTES = [
["TotalTimeSeconds", "totalTimeSeconds"],
["DistanceMeters", "distanceMeters"],
["MaximumSpeed", "maxSpeed"],
["AverageHeartRateBpm", "avgHeartRate"],
["MaximumHeartRateBpm", "maxHeartRate"],
// Extended Lap attributes
["AvgSpeed", "avgSpeed"],
["AvgWatts", "avgWatts"],
["MaxWatts", "maxWatts"]];
function fromEntries(arr) {
const obj = {};
for (const [key, value] of arr) {
obj[key] = value;
}
return obj;
}
function getProperties$1(node, attributeNames) {
const properties = [];
for (const [tag, alias] of attributeNames) {
let elem = get1(node, tag);
if (!elem) {
const elements = node.getElementsByTagNameNS(EXTENSIONS_NS, tag);
if (elements.length) {
elem = elements[0];
}
}
const val = parseFloat(nodeVal(elem));
if (!isNaN(val)) {
properties.push([alias, val]);
}
}
return properties;
}
function coordPair$1(x) {
const lon = nodeVal(get1(x, "LongitudeDegrees"));
const lat = nodeVal(get1(x, "LatitudeDegrees"));
if (!lon.length || !lat.length) {
return null;
}
const ll = [parseFloat(lon), parseFloat(lat)];
const alt = get1(x, "AltitudeMeters");
const heartRate = get1(x, "HeartRateBpm");
const time = get1(x, "Time");
let a;
if (alt) {
a = parseFloat(nodeVal(alt));
if (!isNaN(a)) {
ll.push(a);
}
}
return {
coordinates: ll,
time: time ? nodeVal(time) : null,
heartRate: heartRate ? parseFloat(nodeVal(heartRate)) : null,
extensions: getProperties$1(x, TRACKPOINT_ATTRIBUTES) };
}
function getPoints$1(node, pointname) {
const pts = node.getElementsByTagName(pointname);
const line = [];
const times = [];
if (pts.length < 2) return null; // Invalid line in GeoJSON
const result = { extendedProperties: {} };
for (let i = 0; i < pts.length; i++) {
const c = coordPair$1(pts[i]);
if (c === null) continue;
line.push(c.coordinates);
if (c.time) times.push(c.time);
for (const [alias, value] of c.extensions) {
if (!result.extendedProperties[alias]) {
result.extendedProperties[alias] = Array(pts.length).fill(null);
}
result.extendedProperties[alias][i] = value;
}
}
return Object.assign(result, {
line: line,
times: times });
}
function getLap(node) {
const segments = node.getElementsByTagName("Track");
const track = [];
const times = [];
const allExtendedProperties = [];
let line;
const properties = fromEntries(getProperties$1(node, LAP_ATTRIBUTES));
for (let i = 0; i < segments.length; i++) {
line = getPoints$1(segments[i], "Trackpoint");
if (line) {
track.push(line.line);
if (line.times.length) times.push(line.times);
allExtendedProperties.push(line.extendedProperties);
}
}
for (let i = 0; i < allExtendedProperties.length; i++) {
const extendedProperties = allExtendedProperties[i];
for (const property in extendedProperties) {
if (segments.length === 1) {
properties[property] = line.extendedProperties[property];
} else {
if (!properties[property]) {
properties[property] = track.map((track) =>
Array(track.length).fill(null));
}
properties[property][i] = extendedProperties[property];
}
}
}
if (track.length === 0) return;
if (times.length)
properties.coordTimes = track.length === 1 ? times[0] : times;
return {
type: "Feature",
properties: properties,
geometry: {
type: track.length === 1 ? "LineString" : "MultiLineString",
coordinates: track.length === 1 ? track[0] : track } };
}
function* tcxGen(doc) {
const laps = doc.getElementsByTagName("Lap");
for (let i = 0; i < laps.length; i++) {
const feature = getLap(laps[i]);
if (feature) yield feature;
}
}
function tcx(doc) {
return {
type: "FeatureCollection",
features: Array.from(tcxGen(doc)) };
}
const removeSpace = /\s*/g;
const trimSpace = /^\s*|\s*$/g;
const splitSpace = /\s+/;
// generate a short, numeric hash of a string
function okhash(x) {
if (!x || !x.length) return 0;
let h = 0;
for (let i = 0; i < x.length; i++) {
h = (h << 5) - h + x.charCodeAt(i) | 0;
}
return h;
}
// get one coordinate from a coordinate array, if any
function coord1(v) {
return v.replace(removeSpace, "").split(",").map(parseFloat);
}
// get all coordinates from a coordinate array as [[],[]]
function coord(v) {
return v.replace(trimSpace, "").split(splitSpace).map(coord1);
}
function xml2str(node) {
if (node.xml !== undefined) return node.xml;
if (node.tagName) {
let output = node.tagName;
for (let i = 0; i < node.attributes.length; i++) {
output += node.attributes[i].name + node.attributes[i].value;
}
for (let i = 0; i < node.childNodes.length; i++) {
output += xml2str(node.childNodes[i]);
}
return output;
}
if (node.nodeName === "#text") {
return (node.nodeValue || node.value || "").trim();
}
if (node.nodeName === "#cdata-section") {
return node.nodeValue;
}
return "";
}
const geotypes = ["Polygon", "LineString", "Point", "Track", "gx:Track"];
function kmlColor(properties, elem, prefix) {
let v = nodeVal(get1(elem, "color")) || "";
const colorProp =
prefix == "stroke" || prefix === "fill" ? prefix : prefix + "-color";
if (v.substr(0, 1) === "#") {
v = v.substr(1);
}
if (v.length === 6 || v.length === 3) {
properties[colorProp] = v;
} else if (v.length === 8) {
properties[prefix + "-opacity"] = parseInt(v.substr(0, 2), 16) / 255;
properties[colorProp] =
"#" + v.substr(6, 2) + v.substr(4, 2) + v.substr(2, 2);
}
}
function numericProperty(properties, elem, source, target) {
const val = parseFloat(nodeVal(get1(elem, source)));
if (!isNaN(val)) properties[target] = val;
}
function gxCoords(root) {
let elems = root.getElementsByTagName("coord");
const coords = [];
const times = [];
if (elems.length === 0) elems = root.getElementsByTagName("gx:coord");
for (let i = 0; i < elems.length; i++) {
coords.push(nodeVal(elems[i]).split(" ").map(parseFloat));
}
const timeElems = root.getElementsByTagName("when");
for (let j = 0; j < timeElems.length; j++) times.push(nodeVal(timeElems[j]));
return {
coords: coords,
times: times };
}
function getGeometry(root) {
let geomNode;
let geomNodes;
let i;
let j;
let k;
const geoms = [];
const coordTimes = [];
if (get1(root, "MultiGeometry")) {
return getGeometry(get1(root, "MultiGeometry"));
}
if (get1(root, "MultiTrack")) {
return getGeometry(get1(root, "MultiTrack"));
}
if (get1(root, "gx:MultiTrack")) {
return getGeometry(get1(root, "gx:MultiTrack"));
}
for (i = 0; i < geotypes.length; i++) {
geomNodes = root.getElementsByTagName(geotypes[i]);
if (geomNodes) {
for (j = 0; j < geomNodes.length; j++) {
geomNode = geomNodes[j];
if (geotypes[i] === "Point") {
geoms.push({
type: "Point",
coordinates: coord1(nodeVal(get1(geomNode, "coordinates"))) });
} else if (geotypes[i] === "LineString") {
geoms.push({
type: "LineString",
coordinates: coord(nodeVal(get1(geomNode, "coordinates"))) });
} else if (geotypes[i] === "Polygon") {
const rings = geomNode.getElementsByTagName("LinearRing"),
coords = [];
for (k = 0; k < rings.length; k++) {
coords.push(coord(nodeVal(get1(rings[k], "coordinates"))));
}
geoms.push({
type: "Polygon",
coordinates: coords });
} else if (geotypes[i] === "Track" || geotypes[i] === "gx:Track") {
const track = gxCoords(geomNode);
geoms.push({
type: "LineString",
coordinates: track.coords });
if (track.times.length) coordTimes.push(track.times);
}
}
}
}
return {
geoms: geoms,
coordTimes: coordTimes };
}
function getPlacemark(root, styleIndex, styleMapIndex, styleByHash) {
const geomsAndTimes = getGeometry(root);
let i;
const properties = {};
const name = nodeVal(get1(root, "name"));
const address = nodeVal(get1(root, "address"));
let styleUrl = nodeVal(get1(root, "styleUrl"));
const description = nodeVal(get1(root, "description"));
const timeSpan = get1(root, "TimeSpan");
const timeStamp = get1(root, "TimeStamp");
const extendedData = get1(root, "ExtendedData");
let iconStyle = get1(root, "IconStyle");
let labelStyle = get1(root, "LabelStyle");
let lineStyle = get1(root, "LineStyle");
let polyStyle = get1(root, "PolyStyle");
const visibility = get1(root, "visibility");
if (name) properties.name = name;
if (address) properties.address = address;
if (styleUrl) {
if (styleUrl[0] !== "#") {
styleUrl = "#" + styleUrl;
}
properties.styleUrl = styleUrl;
if (styleIndex[styleUrl]) {
properties.styleHash = styleIndex[styleUrl];
}
if (styleMapIndex[styleUrl]) {
properties.styleMapHash = styleMapIndex[styleUrl];
properties.styleHash = styleIndex[styleMapIndex[styleUrl].normal];
}
// Try to populate the lineStyle or polyStyle since we got the style hash
const style = styleByHash[properties.styleHash];
if (style) {
if (!iconStyle) iconStyle = get1(style, "IconStyle");
if (!labelStyle) labelStyle = get1(style, "LabelStyle");
if (!lineStyle) lineStyle = get1(style, "LineStyle");
if (!polyStyle) polyStyle = get1(style, "PolyStyle");
}
}
if (description) properties.description = description;
if (timeSpan) {
const begin = nodeVal(get1(timeSpan, "begin"));
const end = nodeVal(get1(timeSpan, "end"));
properties.timespan = { begin: begin, end: end };
}
if (timeStamp) {
properties.timestamp = nodeVal(get1(timeStamp, "when"));
}
if (iconStyle) {
kmlColor(properties, iconStyle, "icon");
numericProperty(properties, iconStyle, "scale", "icon-scale");
numericProperty(properties, iconStyle, "heading", "icon-heading");
const hotspot = get1(iconStyle, "hotSpot");
if (hotspot) {
const left = parseFloat(hotspot.getAttribute("x"));
const top = parseFloat(hotspot.getAttribute("y"));
if (!isNaN(left) && !isNaN(top)) properties["icon-offset"] = [left, top];
}
const icon = get1(iconStyle, "Icon");
if (icon) {
const href = nodeVal(get1(icon, "href"));
if (href) properties.icon = href;
}
}
if (labelStyle) {
kmlColor(properties, labelStyle, "label");
numericProperty(properties, labelStyle, "scale", "label-scale");
}
if (lineStyle) {
kmlColor(properties, lineStyle, "stroke");
numericProperty(properties, lineStyle, "width", "stroke-width");
}
if (polyStyle) {
kmlColor(properties, polyStyle, "fill");
const fill = nodeVal(get1(polyStyle, "fill"));
const outline = nodeVal(get1(polyStyle, "outline"));
if (fill)
properties["fill-opacity"] =
fill === "1" ? properties["fill-opacity"] || 1 : 0;
if (outline)
properties["stroke-opacity"] =
outline === "1" ? properties["stroke-opacity"] || 1 : 0;
}
if (extendedData) {
const datas = extendedData.getElementsByTagName("Data"),
simpleDatas = extendedData.getElementsByTagName("SimpleData");
for (i = 0; i < datas.length; i++) {
properties[datas[i].getAttribute("name")] = nodeVal(
get1(datas[i], "value"));
}
for (i = 0; i < simpleDatas.length; i++) {
properties[simpleDatas[i].getAttribute("name")] = nodeVal(simpleDatas[i]);
}
}
if (visibility) {
properties.visibility = nodeVal(visibility);
}
if (geomsAndTimes.coordTimes.length) {
properties.coordTimes =
geomsAndTimes.coordTimes.length === 1 ?
geomsAndTimes.coordTimes[0] :
geomsAndTimes.coordTimes;
}
const feature = {
type: "Feature",
geometry:
geomsAndTimes.geoms.length === 0 ?
null :
geomsAndTimes.geoms.length === 1 ?
geomsAndTimes.geoms[0] :
{
type: "GeometryCollection",
geometries: geomsAndTimes.geoms },
properties: properties };
if (root.getAttribute("id")) feature.id = root.getAttribute("id");
return feature;
}
function* kmlGen(doc) {
// styleindex keeps track of hashed styles in order to match feature
const styleIndex = {};
const styleByHash = {};
// stylemapindex keeps track of style maps to expose in properties
const styleMapIndex = {};
// atomic geospatial types supported by KML - MultiGeometry is
// handled separately
// all root placemarks in the file
const placemarks = doc.getElementsByTagName("Placemark");
const styles = doc.getElementsByTagName("Style");
const styleMaps = doc.getElementsByTagName("StyleMap");
for (let k = 0; k < styles.length; k++) {
const hash = okhash(xml2str(styles[k])).toString(16);
styleIndex["#" + styles[k].getAttribute("id")] = hash;
styleByHash[hash] = styles[k];
}
for (let l = 0; l < styleMaps.length; l++) {
styleIndex["#" + styleMaps[l].getAttribute("id")] = okhash(
xml2str(styleMaps[l])).
toString(16);
const pairs = styleMaps[l].getElementsByTagName("Pair");
const pairsMap = {};
for (let m = 0; m < pairs.length; m++) {
pairsMap[nodeVal(get1(pairs[m], "key"))] = nodeVal(
get1(pairs[m], "styleUrl"));
}
styleMapIndex["#" + styleMaps[l].getAttribute("id")] = pairsMap;
}
for (let j = 0; j < placemarks.length; j++) {
const feature = getPlacemark(
placemarks[j],
styleIndex,
styleMapIndex,
styleByHash);
if (feature) yield feature;
}
}
function kml(doc) {
return {
type: "FeatureCollection",
features: Array.from(kmlGen(doc)) };
}
export { gpx, gpxGen, kml, kmlGen, tcx, tcxGen };
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
JavaScript
1
https://gitee.com/lanchonglujie/kml-to-geojson.git
git@gitee.com:lanchonglujie/kml-to-geojson.git
lanchonglujie
kml-to-geojson
KML转geojson
master

搜索帮助