OpenLayers의 약간 편리한 사용법
131347 단어 geojsonOpenLayerstech
OpenLayers는 하기만 하면 대충 잘 할 수 있는 프로그램 라이브러리이지만 그 편안성 때문에 설정이 어렵다. 항상'그걸 어떻게 했지'를 위해 이용례의 견본을 정리한다.
를 사용하여 좌표 가져오기, 계단식 순서 변경, 투영 방법의 요점, GeoJSON의 표시 방법 등을 사용할 수 있습니다.
클릭 이벤트와 좌표
견본
const container = document.getElementById('popup');
const content = document.getElementById('popup-content');
const closer = document.getElementById('popup-closer');
const overlay = new ol.Overlay({
element: container, // ここで渡した時点でbody内からcontainer要素が一度消える
});
const closeOverlay = function () {
overlay.setPosition(undefined);
closer.blur();
return false;
};
closer.onclick = closeOverlay;
const polygon = {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[0, 0], [90, 0], [90, 60], [0, 60], [0, 0]
]
],
},
};
const vectorSource = new ol.source.Vector();
vectorSource.addFeature(new ol.format.GeoJSON({
dataProjection: "EPSG:4326",
featureProjection: "EPSG:3857",
}).readFeature(polygon));
const map = new ol.Map({
target: 'map',
overlays: [overlay],
layers: [
new ol.layer.Tile({
source: new ol.source.XYZ({
url: "https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg",
attributions: "国土地理院(https://maps.gsi.go.jp/development/ichiran.html)",
tilePixelRatio: window && window.devicePixelRatio ? window.devicePixelRatio : 1
// TileLayerをVectorLayerと一緒に表示してかつmap.forEachLayerAtPixelで値を拾うときは
// tilePixelRatioを適切に設定していないとうまく動かないケースがある
}),
properties: {
id: "Tile"
},
}),
new ol.layer.Vector({
source: vectorSource,
style: new ol.style.Style({
fill: new ol.style.Fill({
color: "rgba(0, 255, 0, 0.5)",
}),
stroke: new ol.style.Stroke({
color: "rgba(0, 255, 0, 1)",
width: 1,
}),
}),
extent: ol.proj.transformExtent([0,0,90,60], "EPSG:4326", "EPSG:3857"),
id: "Vector"
}),
new ol.layer.Graticule({
strokeStyle: new ol.style.Stroke({
color: "rgba(255,0,0,0.9)",
width: 3,
lineDash: [0.5, 4],
}),
showLabels: true,
id: "Graticule"
})
],
view: new ol.View({
center: ol.proj.fromLonLat([40, 40]),
zoom: 1
}),
});
map.on('singleclick', function (evt) {
let layers= "";
map.forEachLayerAtPixel(map.getEventPixel(evt.originalEvent), (layer) => {
const id = layer.get('id');
layers += id + ",";
return false; // trueを返すと初めにヒットしたレイヤーで処理が止まる
}, {
layerFilter: (layer) => {
const extent = layer.getExtent();
// layerにextentが設定されていた場合、クリックがextent内か判定
return !Array.isArray(extent) ||
ol.extent.containsCoordinate(extent, evt.coordinate);
}
});
if(layers.length > 1)
layers = layers.slice(0, layers.length - 1);
content.innerHTML = `
<dl>
<dt>
pixel
</dt>
<dd>
${JSON.stringify(map.getEventPixel(evt.originalEvent))}
</dd>
<dt>
lon/lat
</dt>
<dd>
${JSON.stringify(ol.proj.toLonLat(evt.coordinate))}
</dd>
<dt>
EPSG:3857での座標
</dt>
<dd>
${JSON.stringify(evt.coordinate)}
</dd>
<dt>
クリックしたレイヤー(上から順)
</dt>
<dd>
${layers}
</dd>
</dl>
`;
overlay.setPosition(evt.coordinate);
});
기본 지도인 seamlessphoto, 다각형(왼쪽 아래[0,0], 오른쪽 위[90,60]), OpenLayers에 준비된 위도경도선 도층을 나타낸다.그런 다음 클릭한 지점의 좌표와 해당 지점의 도면층 이름을 표시합니다.
요점
map.forEachLayerAtPixel
의 세 번째 매개변수에 layer Filter를 설정하여 객체 레이어를 필터링할 수 있습니다.이 예에서 extent가 층으로 설정되었을 때, 지점이 extetn의 범위 밖에 있을 때 처리를 건너뜁니다.evt.originalEvent
는 맵의 투영계의 좌표ol.proj.toLonLat(evt.coordinate,)
는 지리 좌표계의 좌표(위도경도)map.getEventPixel(evt.originalEvent))
는 맵의 픽셀 좌표(왼쪽 위는 원점)layer Filter에서 필터링한 예입니다.클릭 지점은 다각형의 도면층의 extent 밖에 있기 때문에 id의 표시에는'Vector'가 포함되지 않습니다.
또한 GeoJSON을 VectorLayer로 표시하는 방법
GeoJSON이 다른 파일로 정의된 경우공식 견본
new VectorLayer({
source: new VectorSource({
url: 'data/geojson/countries.geojson',
format: new GeoJSON(),
})
});
source에서 URL을 지정하는 것이 비교적 쉽다GeoJSON이 JS에 기술된 경우
나는 개인적으로
const vectorSource = new ol.source.Vector();
vectorSource.addFeature(new ol.format.GeoJSON({
dataProjection: "EPSG:4326",
featureProjection: "EPSG:3857",
}).readFeature(polygon));
new ol.layer.Vector({
source: vectorSource
})
이런 문법이 가장 가볍다고 생각한다.계단식 순서
견본
const vectorSource1 = new ol.source.Vector();
vectorSource1.addFeature(new ol.format.GeoJSON({
dataProjection: "EPSG:4326",
featureProjection: "EPSG:3857",
}).readFeature({
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[0, 5], [100, 5], [100, 65], [0, 65], [0, 5]
]
],
},
}));
const vectorSource2 = new ol.source.Vector();
vectorSource2.addFeature(new ol.format.GeoJSON({
dataProjection: "EPSG:4326",
featureProjection: "EPSG:3857",
}).readFeature({
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[40, -10], [80, -10], [80, 20], [40, 20], [40, -10]
]
],
},
}));
const vectorSource3 = new ol.source.Vector();
vectorSource3.addFeature(new ol.format.GeoJSON({
dataProjection: "EPSG:4326",
featureProjection: "EPSG:3857",
}).readFeature({
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[50, 0], [70, 0], [70, 10], [50, 10], [50, 0]
]
],
},
}));
const vectorSource4 = new ol.source.Vector();
vectorSource4.addFeature(new ol.format.GeoJSON({
dataProjection: "EPSG:4326",
featureProjection: "EPSG:3857",
}).readFeature({
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[45, -5], [85, -5], [85, 25], [45, 25], [45, -5]
]
],
},
}));
const layer1 = new ol.layer.Tile({
source: new ol.source.OSM(),
properties: {
id: "layer1"
},
zIndex: 0
});
const layer2 = new ol.layer.Vector({
source: vectorSource1,
style: new ol.style.Style({
fill: new ol.style.Fill({
color: "rgba(128, 128, 128, 0.5)",
}),
stroke: new ol.style.Stroke({
color: "rgba(128, 128, 128, 1)",
width: 1,
}),
}),
properties: {
id: "layer2"
},
zIndex: 1
});
const layer3 = new ol.layer.Tile({
source: new ol.source.XYZ({
url: "https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg",
attributions: "国土地理院(https://maps.gsi.go.jp/development/ichiran.html)",
}),
extent: ol.proj.transformExtent([60, -20, 120, 50], "EPSG:4326", "EPSG:3857"),
properties: {
id: "layer3"
},
zIndex: 2
});
const layer4 = new ol.layer.Group({
layers: [
new ol.layer.Vector({
source: vectorSource2,
style: new ol.style.Style({
fill: new ol.style.Fill({
color: "rgba(255, 0, 0, 0.7)",
}),
stroke: new ol.style.Stroke({
color: "rgba(255, 0, 0, 1)",
width: 2,
}),
}),
properties: {
id: "layer4_1"
}
}),
new ol.layer.Vector({
source: vectorSource3,
style: new ol.style.Style({
fill: new ol.style.Fill({
color: "rgba(0, 0, 255, 0.7)",
}),
stroke: new ol.style.Stroke({
color: "rgba(0, 0, 255, 1)",
width: 2,
}),
}),
properties: {
id: "layer4_2"
},
zIndex: 100 // 最前面
}),
new ol.layer.Vector({
source: vectorSource4,
style: new ol.style.Style({
fill: new ol.style.Fill({
color: "rgba(0, 255, 0, 0.7)",
}),
stroke: new ol.style.Stroke({
color: "rgba(0, 255, 0, 1)",
width: 2,
}),
}),
properties: {
id: "layer4_3"
}
})
],
properties: {
id: "layer4"
},
zIndex: 3
});
const map = new ol.Map({
target: "map",
layers: [
layer1, layer2, layer3, layer4
],
view: new ol.View({
center: ol.proj.fromLonLat([60, 20]),
zoom: 2,
})
});
const layerOrderList = (arr) => {
return "<ul>" +
(arr || map.getLayers().getArray()).reduce((prev, layer)=>{
let child = "";
if(typeof layer.getLayers === "function") {
child = layerOrderList(layer.getLayers().getArray());
}
return prev +
`<li>
<div>${layer.getProperties().id}: ${layer.getZIndex()}</div>${child}
</li>`
}, "")
+ "</ul>";
};
document.getElementById("list").innerHTML = layerOrderList();
document.getElementById("order").addEventListener("change", (e)=>{
if(e.target.value === "asc") {
layer1.setZIndex(0);
layer2.setZIndex(1);
layer3.setZIndex(2);
layer4.setZIndex(3);
}
else if(e.target.value === "desc") {
layer1.setZIndex(0);
layer2.setZIndex(3);
layer3.setZIndex(2);
layer4.setZIndex(1);
}
else {
layer1.setZIndex(10);
}
document.getElementById("list").innerHTML = layerOrderList();
});
레이어의 중첩 순서는 레이어에 설정된 zIndex의 값에 따라 결정됩니다.아무것도 설정하지 않으면 맵에 건네주는 순서대로 하세요.
나중에 setZIndex도 변경할 수 있습니다.
OpenLayers의 특징으로 래스터 레이어와 벡토 레이어를 구분하지 않고 중첩 순서를 설정할 수 있습니다.
타일 A->다각형 A->타일 B처럼 울타리층과 베이커토층을 혼합해 중첩[1]할 수 있다는 것이다.
투영 전환
잉크 카트리지 그래프(EPSG: 3857)
북극역 중심의 투영법(EPSG: 3411)
견본
// EPSG:4326の座標で記述されたポリゴン
const polygon1 = {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[0, 0], [100, 0], [100, 60], [0, 60], [0, 0]
]
],
},
};
// EPSG:3411の座標で記述されたポリゴン
const polygon2 = {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[234300, -608000],
[-1286000, -7000000],
[3780000, -2940000],
[234300, -608000],
]
],
},
};
const getView = (code) => {
const targetProj = code ? ol.proj.get(code) : "EPSG:3857";
return new ol.View({
projection: targetProj,
center: [0, 0],
zoom: 2,
});
};
const getGeoJson = (options) => {
const {srcCode, dstCode} = Object.assign({
srcCode:"EPSG:4326", dstCode:"EPSG:3857"
}, options);
return new ol.format.GeoJSON({
dataProjection: srcCode,
featureProjection: dstCode,
});
};
const transformExtent = (extent, options) => {
const {srcCode, dstCode} = Object.assign({
srcCode:"EPSG:4326", dstCode:"EPSG:3857"
}, options);
return ol.proj.transformExtent(extent, srcCode, dstCode);
}
(async ()=>{
const proj4Def = await fetch("./proj4def.json")
.then(response => response.json());
proj4.defs(
"EPSG:3411",
proj4Def["EPSG:3411"]
);
ol.proj.proj4.register(proj4);
const rasterExtent = [80, 20, 120, 50];
const rasterLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
url: "https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg",
attributions: "国土地理院(https://maps.gsi.go.jp/development/ichiran.html)",
}),
extent: transformExtent(rasterExtent)
});
const vectorSource1 = new ol.source.Vector();
vectorSource1.addFeature(getGeoJson().readFeature(polygon1));
const vectorSource2 = new ol.source.Vector();
vectorSource2.addFeature(getGeoJson({srcCode: "EPSG:3411"}).readFeature(polygon2));
const map = new ol.Map({
target: "map",
layers: [
new ol.layer.Tile({
source: new ol.source.OSM(),
}),
rasterLayer,
new ol.layer.Vector({
source: vectorSource1,
style: new ol.style.Style({
fill: new ol.style.Fill({
color: "rgba(255, 0, 0, 0.4)",
}),
stroke: new ol.style.Stroke({
color: "rgba(255, 0, 0, 1)",
width: 5,
}),
})
}),
new ol.layer.Vector({
source: vectorSource2,
style: new ol.style.Style({
fill: new ol.style.Fill({
color: "rgba(0, 255, 0, 0.4)",
}),
stroke: new ol.style.Stroke({
color: "rgba(0, 255, 0, 1)",
width: 5,
}),
})
})
],
view: getView()
});
document.getElementById("proj").addEventListener("change", (e)=>{
const code = e.target.value;
const newView = getView(code);
map.setView(newView);
// 投影法を切り替えるたびにsourceをクリアして新しい座標系で記述されたfeatureで書き換える
vectorSource1.clear();
vectorSource1.addFeature(getGeoJson({dstCode: code}).readFeature(polygon1));
vectorSource2.clear();
vectorSource2.addFeature(getGeoJson({srcCode: "EPSG:3411", dstCode: code}).readFeature(polygon2));
// 投影法を切り替えるたびにextentを設定し直す
console.log(code, transformExtent(rasterExtent, {dstCode: code}));
rasterLayer.setExtent(transformExtent(rasterExtent, {dstCode: code}));
});
})();
평탄하지 않은 지구를 평면에 강제로 표현하는 방법에는 각양각색의 투영법이 있다.기본적으로 OpenLayers의 EPSG:3857 컴퓨터 그래픽이 View로 지정됩니다.
또한 뷰에 지정된 투영법의 좌표계에서 좌표를 조작하고 가져옵니다.
View에서 투영법 (즉 기본 EPSG: 3857) 을 지정하지 않았는데, center는 항상 위도 경도에 따라 지정됩니다.
지도의 중심에 일본 부근을 표시하고 싶습니다.
const map = new ol.Map({
target: 'map',
view: new ol.View({
center: [140, 45], // NG 正解は ol.proj.fromLonLat([140, 45]),
zoom: 1
}),
});
EPSG:3857, 일본 부근은 [15608704,4423157]
처럼 매우 큰 좌표위이다.[140, 45]
에서 원점에서 거의 이동하지 않는다.이렇게 되면 일본을 중심으로 아프리카 부근이 나타난다.
ol.proj.fromLonLat
등으로 뷰에서 사용하는 투영 시스템의 값을 잘 수정하는 것을 잊지 마세요.또한 OpenLayers와proj4를 조합하면 비교적 간단하고 EPSG:3857 이외의 투영법으로 지도와 도형[2]을 표시할 수 있다.
샘플은 북극 중심의 투영법(EPSG:3411)으로 전환할 수 있다.
proj4의 설정epsg.io은 확인할 수 있고 epsg-indexJSON에 모인 고마운 프로그램 라이브러리도 있으니 사용하세요.
다각형을 표시할 때 뷰의 투영법을 전환하면서 Source 제작을 변경하지 않으면 변경 전 좌표가 제대로 표시되지 않습니다.
또한 GeoJSON은 기본적으로 EPSG:4326에 기술해야 한다. 다른 투영계의 좌표로 기술하더라도 적절하게 설정
new ol.format.GeoJSON
된dataProjection
만 표시하면 된다.마찬가지로 층의 extent도 View의 투영 방법을 전환하는 동시에 변경해야 한다.
GeoJson의 스타일 변경
견본
const labels = ["aaa", "bbb", "ccc"]
const geojson1 = {
"type": "FeatureCollection",
"features": []
};
for(let i=0; i<1000; i++) {
const lon = 360 * Math.random() - 180;
const lat = 160 * Math.random() - 80;
const width = Math.floor(4 * Math.random());
const r = Math.floor(256 * Math.random());
const g = Math.floor(256 * Math.random());
const b = Math.floor(256 * Math.random());
geojson1.features.push({
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[lon, lat + 5], [lon - 4, lat - 2], [lon + 4, lat - 2], [lon, lat + 5]
]
],
},
"properties": {
r, g, b, width
}
})
}
const geojson2 = {
"type": "FeatureCollection",
"features": []
};
for(let i=0; i<1000; i++) {
const lon = 360 * Math.random() - 180;
const lat = 160 * Math.random() - 80;
const text = labels[Math.floor(4 * Math.random())]
geojson2.features.push({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [lon, lat]
},
"properties": {
text
}
})
}
// Feature個別にスタイルを設定しておく
const vectorSource1 = new ol.source.Vector({
features: geojson1.features.map((feature)=>{
const obj = new ol.Feature(new ol.geom.Polygon(feature.geometry.coordinates));
obj.setStyle(
new ol.style.Style({
fill: new ol.style.Fill({
color: `rgba(${feature.properties.r}, ${feature.properties.g}, ${feature.properties.b}, 0.5)`,
}),
stroke: new ol.style.Stroke({
color: `rgba(${feature.properties.r}, ${feature.properties.g}, ${feature.properties.b}, 1)`,
width: feature.properties.width,
})
})
);
return obj
})
});
// Featureにはスタイル未設定
const vectorSource2 = new ol.source.Vector();
vectorSource2.addFeatures(new ol.format.GeoJSON({
dataProjection: "EPSG:4326",
featureProjection: "EPSG:4326",
}).readFeatures(geojson2));
const layer1 = new ol.layer.Tile({
source: new ol.source.OSM(),
});
const layer2 = new ol.layer.Vector({
id: "polygon",
source: vectorSource1,
// sourceのFeatureに設定されたstyleが優先される
style: new ol.style.Style({
fill: new ol.style.Fill({
color: `rgba(0, 0, 255, 0.5)`,
}),
stroke: new ol.style.Stroke({
color: `rgba(0, 255,0 , 1)`,
width: 1,
})
})
});
const layer3 = new ol.layer.Vector({
id: "text",
source: vectorSource2,
// レイヤーにスタイルを設定する。
// 関数を渡すことで、描画時にFeatureの値によってスタイルを変えることができる。
style: (featureObj)=>{
const {
text
} = featureObj.getProperties();
return new ol.style.Style({
text: new ol.style.Text({
text,
stroke: new ol.style.Stroke({color: "#fff", width: 2}),
font: "bold 12px sans-serif"
})
})
}
});
const map = new ol.Map({
target: "map",
layers: [
layer1, layer2, layer3
],
view: new ol.View({
projection: "EPSG:4326",
center: [0, 0],
zoom: 1,
})
});
let selected = null;
map.on('pointermove', (e) => {
if (selected) {
selected.setStyle(undefined);
selected = null;
}
map.forEachFeatureAtPixel(e.pixel, (feature, layer) => {
selected = feature;
feature.setStyle(new ol.style.Style({
text: new ol.style.Text({
text: feature.getProperties().text,
stroke: new ol.style.Stroke({color: "#fff", width: 2}),
font: "bold 24px sans-serif"
})
}));
return true;
}, {
layerFilter: (layer) => {
const id = layer.get("id");
// textレイヤーのfeatureのみスタイルを変える
return id === "text";
}
});
});
샘플은 모두 2000개의 다각형을 나타냈다.다각형이 5000이나 10000을 넘으면 PC에 표시됩니다.태블릿PC와 스마트폰의 디스플레이를 고려하면 다각형의 디스플레이 수량 상한선을 고려하는 것이 좋다.
또는 WebGLPoints 도층 사용을 탐구한다.
GeoJSON의 외관이지만 스타일에 아무것도 지정하지 않으면 모든 도형이 같은 디자인입니다.
new ol.layer.Vector({
source: vectorSource
})
기본 화이트 및 블루 디자인
이번 견본은 두 종류의 벡터 도층을 보여 주었다.
map.forEachFeatureAtPixel
를 사용하여 마우스 위치에서 Feature 스타일을 변경합니다(텍스트 레이어의 글꼴 크기 증가).forEachFeatureAtPixel
는 forEachLayerAtPixel
와 마찬가지로 세 번째 매개변수에 layer Filter를 설정하면 객체 레이어를 필터링할 수 있습니다.Vector Source의 정의 방법은 여러 가지가 있는데 매번 고민이 많고 샘플은 두 가지 형식으로 쓰여져 있다.
저는 개인적으로 함수를 Layer에게 전달하는 스타일을 좋아해서
map.forEachFeatureAtPixel
와 조합할 때 비교적 가볍습니다.총결산
OpenLayersv6의 개인 Tips 컬렉션입니다.
OpenLayers공식 견본는 충실하지만 일본어에서 복잡한 사용법에 대한 자료도 적고, 영어에서는 공식 자료를 제외하고 모두 v6 이전 자료보다 눈에 띄어 도움이 됐으면 합니다.
각주
Leaflet 샘플
Leaflet에서 타일과 다각형의 배치div는 분리되어 있다(.leafllet-tile-pane와.leafllet-overlay-pane). 따라서 타일 A->타일 B->다각형 A의 중첩 순서는 가능하다타일 A->다각형 A->타일 B처럼 타일과 다각형의 층이 혼합되어 중첩될 수 없다(층을 강제로 바꾸는 옵션스의pane라면 실현할 수 있지만 디자인 사상적으로 너무 해서는 안 된다).
다각형보다 앞에 타일을 보여주고 싶은 상황이 얼마나 되는지는 말할 것도 없다.↩︎
Leaflet플러그 인에서 사용할 수 있습니다.↩︎
Reference
이 문제에 관하여(OpenLayers의 약간 편리한 사용법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/yonda/articles/4d39370e30a808텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)