KML/OpenLayers 没有 <Document> 标签的 GroundOverlay 元素未加载到地图上?
KML/OpenLayers GroundOverlay element that doesn't have <Document> tags not loading onto map?
我有一个没有文档标签的 KML 元素,只有像...这样的标签(用...删除了一些不相关的元素)
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<GroundOverlay>
<name>...</name>
<description>...</description>
<Icon><href>...</href></Icon>
<LatLonBox>
...
</LatLonBox>
</GroundOverlay>
</kml>
在 OpenLayers 中,如果 GroundOverlay 周围没有文档标签,我无法加载它。但是,我可以获得一个位置标记,它是没有文档标签的根节点可以正常加载。
有没有办法将带有 GroundOverlay 根节点的 KML 转换为没有文档标签的 KML?它似乎在 Google Earth 和 Cesium 等其他渲染器中加载良好。
OpenLayers 不支持
可以使用地面叠加图块作为 OpenLayers 层的源。到目前为止,我只是通过对坐标、图块大小、图块路径等进行硬编码来做到这一点。手动从 KML 中读取并让 OpenLayers 计算正确的投影,尽管 proj4 确实允许旋转投影,但我还没有整理出数学需要处理旋转。我有一个基于 https://www.dwgwalking.co.uk/garminTryB4Buy.htm The demo http://mikenunn.16mb.com/demo/dwg-aracena-demo.htm 示例数据的演示,使用顶行的 east/west 坐标和左列的 north/south 坐标并忽略非常小的旋转。
更新
我现在已经将其开发为一个潜在的库函数,用于解析 KML 文件以获取地面叠加层和 return ImageStatic 源和图层的图层组。也支持旋转。唯一需要的参数是 KML 文档 url。支持的选项有 attributions
和 crossOrigin
.
这是直接从 KML 文档加载的之前的多播放演示(旋转非常小):
var tileLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
attributions: [
'Powered by Esri',
'Source: Esri, DigitalGlobe, GeoEye, Earthstar Geographics, CNES/Airbus DS, USDA, USGS, AeroGRID, IGN, and the GIS User Community'
],
attributionsCollapsible: false,
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
maxZoom: 23
})
});
var map = new ol.Map({
layers: [tileLayer],
target: 'map',
logo: false,
view: new ol.View({
center:[0, 0],
zoom: 2
})
});
var extent = ol.extent.createEmpty();
var group = kmlOverlay.loadUrl(
'https://www.mikenunn.net/data/dwg/aracena/doc.kml',
{ attributions: 'Copyright David Brawn <a href="https://www.dwgwalking.co.uk" target="_blank">www.dwgwalking.co.uk</a>' }
);
group.getLayers().on('add', function(evt) {
evt.element.once('change:source', function() {
if (evt.element.getSource && evt.element.getSource().getProjection) {
var imageProj = evt.element.getSource().getProjection();
ol.extent.extend(extent, ol.proj.transformExtent(imageProj.getExtent(), imageProj, map.getView().getProjection()));
map.getView().fit(extent);
}
});
});
group.setOpacity(0.7);
map.addLayer(group);
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.map {
width: 100%;
height: 100%;
}
<link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
<!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.5.0/proj4.js"></script>
<script>
kmlOverlay = function() {
function loadUrl ( url,
opt_options // attributions (defaults to undefined), crossOrigin (defaults to 'anonymous')
) {
var options = opt_options || {};
var crossOrigin = options.crossOrigin === undefined ? 'anonymous' : options.crossOrigin;
var group = new ol.layer.Group();
function addLayer(name, extent, url, rotation) { // function to maintain context during async img load
var imageLayer = new ol.layer.Image({
title: name
});
group.getLayers().push(imageLayer);
var imageSize = [];
var img = document.createElement('img');
img.onload = function() {
imageSize[0] = img.width;
imageSize[1] = img.height;
imageLayer.setSource(
source (
extent,
url,
rotation,
imageSize,
{ attributions: options.attributions, crossOrigin: crossOrigin }
)
);
};
img.crossOrigin = crossOrigin;
img.src = url;
}
var last = url.lastIndexOf('/') + 1;
path = url.slice(0, last);
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(xhr.responseText,'text/xml');
var elements = xmlDoc.getElementsByTagName('GroundOverlay');
for (var i=0; i<elements.length; i++) {
var name;
if (elements[i].getElementsByTagName('rotation').length > 0) {
name = elements[i].getElementsByTagName('name')[0].childNodes[0].nodeValue;
}
var href = elements[i].getElementsByTagName('href')[0].childNodes[0].nodeValue;
if (href.indexOf('http:') != 0 && href.indexOf('https:') != 0) {
href = path + href;
}
var north = Number(elements[i].getElementsByTagName('north')[0].childNodes[0].nodeValue);
var south = Number(elements[i].getElementsByTagName('south')[0].childNodes[0].nodeValue);
var east = Number(elements[i].getElementsByTagName('east')[0].childNodes[0].nodeValue);
var west = Number(elements[i].getElementsByTagName('west')[0].childNodes[0].nodeValue);
var rotation = 0;
if (elements[i].getElementsByTagName('rotation').length > 0) {
rotation = Number(elements[i].getElementsByTagName('rotation')[0].childNodes[0].nodeValue);
}
addLayer(name, [west, south, east, north], href, rotation);
}
}
xhr.send();
return group;
}
function source ( kmlExtent, // KMLs specify the extent the unrotated image would occupy
url,
rotation,
imageSize,
opt_options // attributions, crossOrigin (default to undefined)
) {
var options = opt_options || {};
// calculate latitude of true scale of equidistant cylindrical projection based on pixels per degree on each axis
proj4.defs('EPSG:' + url, '+proj=eqc +lat_ts=' +
(Math.acos((ol.extent.getHeight(kmlExtent)/imageSize[1])
/(ol.extent.getWidth(kmlExtent)/imageSize[0]))*180/Math.PI) +
' +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
if (ol.proj.proj4 && ol.proj.proj4.register) { ol.proj.proj4.register(proj4); } // if OL5 register proj4
// convert the extents to source projection coordinates
var projection = ol.proj.get('EPSG:' + url);
var projExtent = ol.proj.transformExtent(kmlExtent, 'EPSG:4326', projection);
var angle = -rotation * Math.PI/180;
function rotateTransform(coordinate) {
var point = new ol.geom.Point(coordinate);
point.rotate(angle, ol.extent.getCenter(projExtent));
return point.getCoordinates();
}
function normalTransform(coordinate) {
var point = new ol.geom.Point(coordinate);
point.rotate(-angle, ol.extent.getCenter(projExtent));
return point.getCoordinates();
}
var rotatedProjection = new ol.proj.Projection({
code: 'EPSG:' + url + ':rotation:' + rotation,
units: 'm',
extent: projExtent
});
ol.proj.addProjection(rotatedProjection);
ol.proj.addCoordinateTransforms('EPSG:4326', rotatedProjection,
function(coordinate) {
return rotateTransform(ol.proj.transform(coordinate, 'EPSG:4326', projection));
},
function(coordinate) {
return ol.proj.transform(normalTransform(coordinate), projection, 'EPSG:4326');
}
);
ol.proj.addCoordinateTransforms('EPSG:3857', rotatedProjection,
function(coordinate) {
return rotateTransform(ol.proj.transform(coordinate, 'EPSG:3857', projection));
},
function(coordinate) {
return ol.proj.transform(normalTransform(coordinate), projection, 'EPSG:3857');
}
);
return new ol.source.ImageStatic({
projection: rotatedProjection,
url: url,
imageExtent: projExtent,
attributions: options.attributions,
crossOrigin: options.crossOrigin
});
}
return {
"loadUrl" : loadUrl,
"source" : source
}
} ();
</script>
<div id="map" class="map"></div>
这是一个非常显眼的旋转覆盖演示,来自 https://renenyffenegger.ch/notes/tools/Google-Earth/kml/index
var tileLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
attributions: [
'Powered by Esri',
'Source: Esri, DigitalGlobe, GeoEye, Earthstar Geographics, CNES/Airbus DS, USDA, USGS, AeroGRID, IGN, and the GIS User Community'
],
//attributionsCollapsible: false,
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
maxZoom: 23
})
});
var map = new ol.Map({
layers: [tileLayer],
target: 'map',
logo: false,
view: new ol.View({
center:[0, 0],
zoom: 2
})
});
var extent = ol.extent.createEmpty();
var group = kmlOverlay.loadUrl(
'https://raw.githubusercontent.com/ReneNyffenegger/about-GoogleEarth/master/kml/GroundOverlay.kml'
);
group.getLayers().once('add', function(evt) {
evt.element.once('change:source', function() {
if (evt.element.getSource && evt.element.getSource().getProjection) {
var imageProj = evt.element.getSource().getProjection();
ol.extent.extend(extent, ol.proj.transformExtent(imageProj.getExtent(), imageProj, map.getView().getProjection()));
map.getView().fit(extent, { constrainResolution: false });
}
});
});
group.setOpacity(0.8);
map.addLayer(group);
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.map {
width: 100%;
height: 100%;
}
<link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
<!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.5.0/proj4.js"></script>
<script>
kmlOverlay = function() {
function loadUrl ( url,
opt_options // attributions (defaults to undefined), crossOrigin (defaults to 'anonymous')
) {
var options = opt_options || {};
var crossOrigin = options.crossOrigin === undefined ? 'anonymous' : options.crossOrigin;
var group = new ol.layer.Group();
function addLayer(name, extent, url, rotation) { // function to maintain context during async img load
var imageLayer = new ol.layer.Image({
title: name
});
group.getLayers().push(imageLayer);
var imageSize = [];
var img = document.createElement('img');
img.onload = function() {
imageSize[0] = img.width;
imageSize[1] = img.height;
imageLayer.setSource(
source (
extent,
url,
rotation,
imageSize,
{ attributions: options.attributions, crossOrigin: crossOrigin }
)
);
};
img.crossOrigin = crossOrigin;
img.src = url;
}
var last = url.lastIndexOf('/') + 1;
path = url.slice(0, last);
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(xhr.responseText,'text/xml');
var elements = xmlDoc.getElementsByTagName('GroundOverlay');
for (var i=0; i<elements.length; i++) {
var name;
if (elements[i].getElementsByTagName('rotation').length > 0) {
name = elements[i].getElementsByTagName('name')[0].childNodes[0].nodeValue;
}
var href = elements[i].getElementsByTagName('href')[0].childNodes[0].nodeValue;
if (href.indexOf('http:') != 0 && href.indexOf('https:') != 0) {
href = path + href;
}
var north = Number(elements[i].getElementsByTagName('north')[0].childNodes[0].nodeValue);
var south = Number(elements[i].getElementsByTagName('south')[0].childNodes[0].nodeValue);
var east = Number(elements[i].getElementsByTagName('east')[0].childNodes[0].nodeValue);
var west = Number(elements[i].getElementsByTagName('west')[0].childNodes[0].nodeValue);
var rotation = 0;
if (elements[i].getElementsByTagName('rotation').length > 0) {
rotation = Number(elements[i].getElementsByTagName('rotation')[0].childNodes[0].nodeValue);
}
addLayer(name, [west, south, east, north], href, rotation);
}
}
xhr.send();
return group;
}
function source ( kmlExtent, // KMLs specify the extent the unrotated image would occupy
url,
rotation,
imageSize,
opt_options // attributions, crossOrigin (default to undefined)
) {
var options = opt_options || {};
// calculate latitude of true scale of equidistant cylindrical projection based on pixels per degree on each axis
proj4.defs('EPSG:' + url, '+proj=eqc +lat_ts=' +
(Math.acos((ol.extent.getHeight(kmlExtent)/imageSize[1])
/(ol.extent.getWidth(kmlExtent)/imageSize[0]))*180/Math.PI) +
' +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
if (ol.proj.proj4 && ol.proj.proj4.register) { ol.proj.proj4.register(proj4); } // if OL5 register proj4
// convert the extents to source projection coordinates
var projection = ol.proj.get('EPSG:' + url);
var projExtent = ol.proj.transformExtent(kmlExtent, 'EPSG:4326', projection);
var angle = -rotation * Math.PI/180;
function rotateTransform(coordinate) {
var point = new ol.geom.Point(coordinate);
point.rotate(angle, ol.extent.getCenter(projExtent));
return point.getCoordinates();
}
function normalTransform(coordinate) {
var point = new ol.geom.Point(coordinate);
point.rotate(-angle, ol.extent.getCenter(projExtent));
return point.getCoordinates();
}
var rotatedProjection = new ol.proj.Projection({
code: 'EPSG:' + url + ':rotation:' + rotation,
units: 'm',
extent: projExtent
});
ol.proj.addProjection(rotatedProjection);
ol.proj.addCoordinateTransforms('EPSG:4326', rotatedProjection,
function(coordinate) {
return rotateTransform(ol.proj.transform(coordinate, 'EPSG:4326', projection));
},
function(coordinate) {
return ol.proj.transform(normalTransform(coordinate), projection, 'EPSG:4326');
}
);
ol.proj.addCoordinateTransforms('EPSG:3857', rotatedProjection,
function(coordinate) {
return rotateTransform(ol.proj.transform(coordinate, 'EPSG:3857', projection));
},
function(coordinate) {
return ol.proj.transform(normalTransform(coordinate), projection, 'EPSG:3857');
}
);
return new ol.source.ImageStatic({
projection: rotatedProjection,
url: url,
imageExtent: projExtent,
attributions: options.attributions,
crossOrigin: options.crossOrigin
});
}
return {
"loadUrl" : loadUrl,
"source" : source
}
} ();
</script>
<div id="map" class="map"></div>
我有一个没有文档标签的 KML 元素,只有像...这样的标签(用...删除了一些不相关的元素)
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<GroundOverlay>
<name>...</name>
<description>...</description>
<Icon><href>...</href></Icon>
<LatLonBox>
...
</LatLonBox>
</GroundOverlay>
</kml>
在 OpenLayers 中,如果 GroundOverlay 周围没有文档标签,我无法加载它。但是,我可以获得一个位置标记,它是没有文档标签的根节点可以正常加载。
有没有办法将带有 GroundOverlay 根节点的 KML 转换为没有文档标签的 KML?它似乎在 Google Earth 和 Cesium 等其他渲染器中加载良好。
OpenLayers 不支持
可以使用地面叠加图块作为 OpenLayers 层的源。到目前为止,我只是通过对坐标、图块大小、图块路径等进行硬编码来做到这一点。手动从 KML 中读取并让 OpenLayers 计算正确的投影,尽管 proj4 确实允许旋转投影,但我还没有整理出数学需要处理旋转。我有一个基于 https://www.dwgwalking.co.uk/garminTryB4Buy.htm The demo http://mikenunn.16mb.com/demo/dwg-aracena-demo.htm 示例数据的演示,使用顶行的 east/west 坐标和左列的 north/south 坐标并忽略非常小的旋转。
更新
我现在已经将其开发为一个潜在的库函数,用于解析 KML 文件以获取地面叠加层和 return ImageStatic 源和图层的图层组。也支持旋转。唯一需要的参数是 KML 文档 url。支持的选项有 attributions
和 crossOrigin
.
这是直接从 KML 文档加载的之前的多播放演示(旋转非常小):
var tileLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
attributions: [
'Powered by Esri',
'Source: Esri, DigitalGlobe, GeoEye, Earthstar Geographics, CNES/Airbus DS, USDA, USGS, AeroGRID, IGN, and the GIS User Community'
],
attributionsCollapsible: false,
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
maxZoom: 23
})
});
var map = new ol.Map({
layers: [tileLayer],
target: 'map',
logo: false,
view: new ol.View({
center:[0, 0],
zoom: 2
})
});
var extent = ol.extent.createEmpty();
var group = kmlOverlay.loadUrl(
'https://www.mikenunn.net/data/dwg/aracena/doc.kml',
{ attributions: 'Copyright David Brawn <a href="https://www.dwgwalking.co.uk" target="_blank">www.dwgwalking.co.uk</a>' }
);
group.getLayers().on('add', function(evt) {
evt.element.once('change:source', function() {
if (evt.element.getSource && evt.element.getSource().getProjection) {
var imageProj = evt.element.getSource().getProjection();
ol.extent.extend(extent, ol.proj.transformExtent(imageProj.getExtent(), imageProj, map.getView().getProjection()));
map.getView().fit(extent);
}
});
});
group.setOpacity(0.7);
map.addLayer(group);
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.map {
width: 100%;
height: 100%;
}
<link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
<!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.5.0/proj4.js"></script>
<script>
kmlOverlay = function() {
function loadUrl ( url,
opt_options // attributions (defaults to undefined), crossOrigin (defaults to 'anonymous')
) {
var options = opt_options || {};
var crossOrigin = options.crossOrigin === undefined ? 'anonymous' : options.crossOrigin;
var group = new ol.layer.Group();
function addLayer(name, extent, url, rotation) { // function to maintain context during async img load
var imageLayer = new ol.layer.Image({
title: name
});
group.getLayers().push(imageLayer);
var imageSize = [];
var img = document.createElement('img');
img.onload = function() {
imageSize[0] = img.width;
imageSize[1] = img.height;
imageLayer.setSource(
source (
extent,
url,
rotation,
imageSize,
{ attributions: options.attributions, crossOrigin: crossOrigin }
)
);
};
img.crossOrigin = crossOrigin;
img.src = url;
}
var last = url.lastIndexOf('/') + 1;
path = url.slice(0, last);
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(xhr.responseText,'text/xml');
var elements = xmlDoc.getElementsByTagName('GroundOverlay');
for (var i=0; i<elements.length; i++) {
var name;
if (elements[i].getElementsByTagName('rotation').length > 0) {
name = elements[i].getElementsByTagName('name')[0].childNodes[0].nodeValue;
}
var href = elements[i].getElementsByTagName('href')[0].childNodes[0].nodeValue;
if (href.indexOf('http:') != 0 && href.indexOf('https:') != 0) {
href = path + href;
}
var north = Number(elements[i].getElementsByTagName('north')[0].childNodes[0].nodeValue);
var south = Number(elements[i].getElementsByTagName('south')[0].childNodes[0].nodeValue);
var east = Number(elements[i].getElementsByTagName('east')[0].childNodes[0].nodeValue);
var west = Number(elements[i].getElementsByTagName('west')[0].childNodes[0].nodeValue);
var rotation = 0;
if (elements[i].getElementsByTagName('rotation').length > 0) {
rotation = Number(elements[i].getElementsByTagName('rotation')[0].childNodes[0].nodeValue);
}
addLayer(name, [west, south, east, north], href, rotation);
}
}
xhr.send();
return group;
}
function source ( kmlExtent, // KMLs specify the extent the unrotated image would occupy
url,
rotation,
imageSize,
opt_options // attributions, crossOrigin (default to undefined)
) {
var options = opt_options || {};
// calculate latitude of true scale of equidistant cylindrical projection based on pixels per degree on each axis
proj4.defs('EPSG:' + url, '+proj=eqc +lat_ts=' +
(Math.acos((ol.extent.getHeight(kmlExtent)/imageSize[1])
/(ol.extent.getWidth(kmlExtent)/imageSize[0]))*180/Math.PI) +
' +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
if (ol.proj.proj4 && ol.proj.proj4.register) { ol.proj.proj4.register(proj4); } // if OL5 register proj4
// convert the extents to source projection coordinates
var projection = ol.proj.get('EPSG:' + url);
var projExtent = ol.proj.transformExtent(kmlExtent, 'EPSG:4326', projection);
var angle = -rotation * Math.PI/180;
function rotateTransform(coordinate) {
var point = new ol.geom.Point(coordinate);
point.rotate(angle, ol.extent.getCenter(projExtent));
return point.getCoordinates();
}
function normalTransform(coordinate) {
var point = new ol.geom.Point(coordinate);
point.rotate(-angle, ol.extent.getCenter(projExtent));
return point.getCoordinates();
}
var rotatedProjection = new ol.proj.Projection({
code: 'EPSG:' + url + ':rotation:' + rotation,
units: 'm',
extent: projExtent
});
ol.proj.addProjection(rotatedProjection);
ol.proj.addCoordinateTransforms('EPSG:4326', rotatedProjection,
function(coordinate) {
return rotateTransform(ol.proj.transform(coordinate, 'EPSG:4326', projection));
},
function(coordinate) {
return ol.proj.transform(normalTransform(coordinate), projection, 'EPSG:4326');
}
);
ol.proj.addCoordinateTransforms('EPSG:3857', rotatedProjection,
function(coordinate) {
return rotateTransform(ol.proj.transform(coordinate, 'EPSG:3857', projection));
},
function(coordinate) {
return ol.proj.transform(normalTransform(coordinate), projection, 'EPSG:3857');
}
);
return new ol.source.ImageStatic({
projection: rotatedProjection,
url: url,
imageExtent: projExtent,
attributions: options.attributions,
crossOrigin: options.crossOrigin
});
}
return {
"loadUrl" : loadUrl,
"source" : source
}
} ();
</script>
<div id="map" class="map"></div>
这是一个非常显眼的旋转覆盖演示,来自 https://renenyffenegger.ch/notes/tools/Google-Earth/kml/index
var tileLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
attributions: [
'Powered by Esri',
'Source: Esri, DigitalGlobe, GeoEye, Earthstar Geographics, CNES/Airbus DS, USDA, USGS, AeroGRID, IGN, and the GIS User Community'
],
//attributionsCollapsible: false,
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
maxZoom: 23
})
});
var map = new ol.Map({
layers: [tileLayer],
target: 'map',
logo: false,
view: new ol.View({
center:[0, 0],
zoom: 2
})
});
var extent = ol.extent.createEmpty();
var group = kmlOverlay.loadUrl(
'https://raw.githubusercontent.com/ReneNyffenegger/about-GoogleEarth/master/kml/GroundOverlay.kml'
);
group.getLayers().once('add', function(evt) {
evt.element.once('change:source', function() {
if (evt.element.getSource && evt.element.getSource().getProjection) {
var imageProj = evt.element.getSource().getProjection();
ol.extent.extend(extent, ol.proj.transformExtent(imageProj.getExtent(), imageProj, map.getView().getProjection()));
map.getView().fit(extent, { constrainResolution: false });
}
});
});
group.setOpacity(0.8);
map.addLayer(group);
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.map {
width: 100%;
height: 100%;
}
<link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
<!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.5.0/proj4.js"></script>
<script>
kmlOverlay = function() {
function loadUrl ( url,
opt_options // attributions (defaults to undefined), crossOrigin (defaults to 'anonymous')
) {
var options = opt_options || {};
var crossOrigin = options.crossOrigin === undefined ? 'anonymous' : options.crossOrigin;
var group = new ol.layer.Group();
function addLayer(name, extent, url, rotation) { // function to maintain context during async img load
var imageLayer = new ol.layer.Image({
title: name
});
group.getLayers().push(imageLayer);
var imageSize = [];
var img = document.createElement('img');
img.onload = function() {
imageSize[0] = img.width;
imageSize[1] = img.height;
imageLayer.setSource(
source (
extent,
url,
rotation,
imageSize,
{ attributions: options.attributions, crossOrigin: crossOrigin }
)
);
};
img.crossOrigin = crossOrigin;
img.src = url;
}
var last = url.lastIndexOf('/') + 1;
path = url.slice(0, last);
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(xhr.responseText,'text/xml');
var elements = xmlDoc.getElementsByTagName('GroundOverlay');
for (var i=0; i<elements.length; i++) {
var name;
if (elements[i].getElementsByTagName('rotation').length > 0) {
name = elements[i].getElementsByTagName('name')[0].childNodes[0].nodeValue;
}
var href = elements[i].getElementsByTagName('href')[0].childNodes[0].nodeValue;
if (href.indexOf('http:') != 0 && href.indexOf('https:') != 0) {
href = path + href;
}
var north = Number(elements[i].getElementsByTagName('north')[0].childNodes[0].nodeValue);
var south = Number(elements[i].getElementsByTagName('south')[0].childNodes[0].nodeValue);
var east = Number(elements[i].getElementsByTagName('east')[0].childNodes[0].nodeValue);
var west = Number(elements[i].getElementsByTagName('west')[0].childNodes[0].nodeValue);
var rotation = 0;
if (elements[i].getElementsByTagName('rotation').length > 0) {
rotation = Number(elements[i].getElementsByTagName('rotation')[0].childNodes[0].nodeValue);
}
addLayer(name, [west, south, east, north], href, rotation);
}
}
xhr.send();
return group;
}
function source ( kmlExtent, // KMLs specify the extent the unrotated image would occupy
url,
rotation,
imageSize,
opt_options // attributions, crossOrigin (default to undefined)
) {
var options = opt_options || {};
// calculate latitude of true scale of equidistant cylindrical projection based on pixels per degree on each axis
proj4.defs('EPSG:' + url, '+proj=eqc +lat_ts=' +
(Math.acos((ol.extent.getHeight(kmlExtent)/imageSize[1])
/(ol.extent.getWidth(kmlExtent)/imageSize[0]))*180/Math.PI) +
' +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
if (ol.proj.proj4 && ol.proj.proj4.register) { ol.proj.proj4.register(proj4); } // if OL5 register proj4
// convert the extents to source projection coordinates
var projection = ol.proj.get('EPSG:' + url);
var projExtent = ol.proj.transformExtent(kmlExtent, 'EPSG:4326', projection);
var angle = -rotation * Math.PI/180;
function rotateTransform(coordinate) {
var point = new ol.geom.Point(coordinate);
point.rotate(angle, ol.extent.getCenter(projExtent));
return point.getCoordinates();
}
function normalTransform(coordinate) {
var point = new ol.geom.Point(coordinate);
point.rotate(-angle, ol.extent.getCenter(projExtent));
return point.getCoordinates();
}
var rotatedProjection = new ol.proj.Projection({
code: 'EPSG:' + url + ':rotation:' + rotation,
units: 'm',
extent: projExtent
});
ol.proj.addProjection(rotatedProjection);
ol.proj.addCoordinateTransforms('EPSG:4326', rotatedProjection,
function(coordinate) {
return rotateTransform(ol.proj.transform(coordinate, 'EPSG:4326', projection));
},
function(coordinate) {
return ol.proj.transform(normalTransform(coordinate), projection, 'EPSG:4326');
}
);
ol.proj.addCoordinateTransforms('EPSG:3857', rotatedProjection,
function(coordinate) {
return rotateTransform(ol.proj.transform(coordinate, 'EPSG:3857', projection));
},
function(coordinate) {
return ol.proj.transform(normalTransform(coordinate), projection, 'EPSG:3857');
}
);
return new ol.source.ImageStatic({
projection: rotatedProjection,
url: url,
imageExtent: projExtent,
attributions: options.attributions,
crossOrigin: options.crossOrigin
});
}
return {
"loadUrl" : loadUrl,
"source" : source
}
} ();
</script>
<div id="map" class="map"></div>