KorailMap/static/ol-helper.js

772 lines
20 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Openlayers Map
* author: khy
* date: 23.09.07
* */
const proj = {
viewProjection: "EPSG:3857",
WGS84Projection: "EPSG:4326",
};
Object.freeze(proj);
Array.prototype.toWGS84 = function () {
if (this.length === 2) {
//coordinate
return ol.proj.transform(this, proj.viewProjection, proj.WGS84Projection);
} else if (this.length === 4) {
//extent
return ol.proj.transformExtent(this, proj.viewProjection, proj.WGS84Projection);
} else {
return this;
}
};
Array.prototype.toEPSG3857 = function () {
if (this.length === 2) {
//coordinate
return ol.proj.transform(this, proj.WGS84Projection, proj.viewProjection);
} else if (this.length === 4) {
//extent
return ol.proj.transformExtent(this, proj.WGS84Projection, proj.viewProjection);
} else {
return this;
}
};
ol.Map.prototype.getProxyURL = function (url) {
return `${document.location.protocol}//${document.location.host}/proxy/?${url ? url : ""}`;
};
ol.Map.prototype.addBaseTileLayer = function (options) {
let host = 'gis.korail.com';
function createBaseTileLayer(map) {
const proxy = map.getProxyURL();
const base = new ol.layer.Tile({
name: "base",
source: new ol.source.XYZ({
tileUrlFunction: (zxy) => {
const [z, x, y] = zxy;
if (z < 5 || z > 15) {
return;
}
return `https://${host}/tilemap/background/${z}/${x}/${y}.png`;
},
minZoom: 5,
maxZoom: 15,
}),
zIndex: 0,
});
if (options === "korean") {
const label = new ol.layer.Tile({
name: "base-label",
source: new ol.source.XYZ({
tileUrlFunction: (zxy) => {
const [z, x, y] = zxy;
if (z < 5 || z > 15) {
return;
}
return `https://${host}/tilemap/korean/${z}/${x}/${y}.png`;
},
minZoom: 5,
maxZoom: 15,
}),
zIndex: 0,
});
const layergroup = new ol.layer.Group({
layers: [base, label],
});
return layergroup;
} else {
return base;
}
}
const layer = createBaseTileLayer(this);
this.addLayer(layer);
return layer;
};
ol.Map.prototype.createBaseLayerButton = function () {
const mapElement = this.getTargetElement();
if (!mapElement.querySelector("button[usage=base]")) {
const toggleButton = document.createElement("button");
toggleButton.setAttribute("usage", "base");
toggleButton.textContent = "";
toggleButton.style.position = "absolute";
toggleButton.style.top = "0px";
toggleButton.style.left = "0px";
toggleButton.style.margin = "10px";
toggleButton.style.marginLeft = "42px";
toggleButton.style.width = "30px";
toggleButton.style.height = "30px";
toggleButton.style.borderRadius = "50%";
toggleButton.style.border = "1px solid gray";
toggleButton.style.background = "white";
toggleButton.style.color = "black";
toggleButton.style.textAlign = "center";
let status = false;
const toggleLayer = () => {
this.getAllLayers()
.filter((l) => l.get("name") !== "base" && l.get("name") !== "base-label" && l.get("name") !== "grid-tile")
.forEach((l) => {
l.setVisible(status);
});
status = !status;
toggleButton.style.background = status ? "dodgerblue" : "white";
toggleButton.style.color = status ? "white" : "black";
if (status) {
viewChange(null, null);
map.getView().setConstrainResolution(true);
} else {
map.getView().setConstrainResolution(false);
viewChange(0, document.querySelector("ul.notice > li"));
}
};
toggleButton.onclick = toggleLayer;
let zoomLevel = undefined;
this.on("moveend", (view) => {
const nowZoomLevel = Number(this.getView().getZoom().toFixed());
if (zoomLevel !== nowZoomLevel) {
zoomLevel = nowZoomLevel;
toggleButton.textContent = zoomLevel;
}
});
mapElement.appendChild(toggleButton);
}
};
ol.Map.prototype.createGridTiles = function () {
const mapElement = this.getTargetElement();
if (!mapElement.querySelector("button[usage=grid-tile]")) {
const toggleButton = document.createElement("button");
toggleButton.setAttribute("usage", "grid-tile");
toggleButton.textContent = "";
toggleButton.style.position = "absolute";
toggleButton.style.top = "0px";
toggleButton.style.left = "0px";
toggleButton.style.margin = "10px";
toggleButton.style.width = "30px";
toggleButton.style.height = "30px";
toggleButton.style.borderRadius = "50%";
toggleButton.style.border = "1px solid gray";
toggleButton.style.background = "white";
toggleButton.style.color = "black";
toggleButton.style.textAlign = "center";
const toggleLayer = () => {
const layer = this.getLayer("grid-tile");
if (layer) {
const currentVisible = layer.getVisible();
const postVisible = !currentVisible;
this.getAllLayers()
.filter((l) => l.get("name") !== "base" && l.get("name") !== "base-label" && l.get("name") !== "grid-tile")
.forEach((l) => {
l.setVisible(currentVisible);
});
layer.setVisible(postVisible);
toggleButton.style.background = postVisible ? "dodgerblue" : "white";
toggleButton.style.color = postVisible ? "white" : "black";
if (postVisible) {
viewChange(null, null);
} else {
viewChange(0, document.querySelector("ul.notice > li"));
}
}
};
toggleButton.onclick = toggleLayer;
mapElement.appendChild(toggleButton);
}
if (!this.getLayer("grid-tile")) {
const tileSize = 256;
const canvas = document.createElement("canvas");
canvas.width = tileSize;
canvas.height = tileSize;
const context = canvas.getContext("2d");
context.strokeStyle = "gray";
context.textAlign = "center";
const lineHeight = 30;
context.font = `${lineHeight - 6}px sans-serif`;
const layer = new ol.layer.WebGLTile({
name: "grid-tile",
source: new ol.source.DataTile({
loader: (z, x, y) => {
const half = tileSize / 2;
context.clearRect(0, 0, tileSize, tileSize);
// context.fillStyle = 'rgba(255, 255, 255, 0.7)';
// context.fillRect(0, 0, tileSize, tileSize);
context.fillStyle = "black";
context.fillText(`z: ${z}`, half, half - lineHeight);
context.fillText(`x: ${x}`, half, half);
context.fillText(`y: ${y}`, half, half + lineHeight);
context.strokeRect(0, 0, tileSize, tileSize);
const data = context.getImageData(0, 0, tileSize, tileSize).data;
return new Uint8Array(data.buffer);
},
}),
zIndex: 999,
});
layer.setVisible(false);
this.addLayer(layer);
}
};
ol.source.Vector.fromGeoJSON = function (geojson) {
return new ol.source.Vector({
features: new ol.format.GeoJSON().readFeatures(geojson, { featureProjection: proj.viewProjection }),
format: new ol.format.GeoJSON(),
strategy: ol.loadingstrategy.bbox,
});
};
ol.Map.prototype.addRegionalLayer = async function () {
const regional_lines = new Array(8);
async function createRegionalLine(url, index) {
return new Promise(async (resolve, reject) => {
try {
const response = await fetch(url);
const json = await response.json();
insertLine(json, index);
resolve();
} catch {
reject();
}
});
}
// 권역 라인이 포인트 형식으로 되어있어서 라인형식으로 바꾸고 데이터 교체하는 함수
function insertLine(json, i) {
const coordinates = json.features.map((point) => {
const converted = point.geometry.coordinates.toEPSG3857();
return converted;
});
const geomLineString = new ol.geom.LineString(coordinates);
const featureLineString = new ol.Feature({ geometry: geomLineString });
regional_lines[i] = featureLineString;
}
// $.getJOSN 으로 json 데이터 읽어 드리고 insertLine 은 현재 json 파일이 line 형식이 아닌 포인트
// 형식으로
// 만들었음
// 포인트 형식을 만든 이유는 수정시 포인트 형식이 필요해서 우선 포인트 형식으로 저장
await Promise.all([
createRegionalLine("resource/Regional_point_chungbuk.json", 0),
createRegionalLine("resource/Regional_point_gang.json", 1),
createRegionalLine("resource/Regional_point_seoul_east.json", 2),
createRegionalLine("resource/Regional_point_seoul_west.json", 3),
createRegionalLine("resource/Regional_point_daejeon_chungnam.json", 4),
createRegionalLine("resource/Regional_point_jeonbuk.json", 5),
createRegionalLine("resource/Regional_point_jeonnam.json", 6),
createRegionalLine("resource/Regional_point_daegu.json", 7),
]);
const regionalSource = new ol.source.Vector({
features: regional_lines,
});
this.addVectorLayer({
name: "Regional_Line",
source: regionalSource,
style: layerStyles.regional,
zIndex: 0,
});
};
/**
* WebGL Layer
* author: khy
*/
const canvas = document.createElement("canvas");
const gl = canvas?.getContext("webgl") || canvas?.getContext("experimental-webgl");
const webglAvailable = gl instanceof WebGLRenderingContext;
class WebGLVectorLayer extends ol.layer.Layer {
constructor(params) {
super(params);
this.style = params.style;
}
createRenderer() {
const style = this.style;
return new ol.renderer.webgl.VectorLayer(this, {
style,
});
}
}
function VectorLayer(params) {
return new ol.layer.Vector(params);
//return (webglAvailable) ? new WebGLVectorLayer(params) : new ol.layer.Vector(params);
}
function PointsLayer(params) {
const hasStyleFunction = typeof params?.style === "function";
return (webglAvailable && !hasStyleFunction) ? new ol.layer.WebGLPoints(params) : new ol.layer.Vector(params);
}
/**
* ol.Map prototype 확장
* @param ...args
* @returns
*/
ol.Map.prototype.addLayerWithType = async function (...args) {
const LayerType = args[0];
args = args[1];
return new Promise(async (resolve, reject) => {
try {
let url, fetchOption, layerOption;
let response, geojson;
switch (args.length) {
case 3:
if (
typeof args[0] === "string" && //fetch url
typeof args[1] === "object" && //fetch option
typeof args[2] === "object"
) {
//layer option
(url = args[0]), (fetchOption = args[1]), (layerOption = args[2]);
response = await fetch(url, fetchOption);
geojson = await response.json();
layerOption.source = await ol.source.Vector.fromGeoJSON(geojson);
} else {
reject();
}
break;
case 2:
if (
typeof args[0] === "string" && //fetch url
typeof args[1] === "object"
) {
//layer option
(url = args[0]), (layerOption = args[1]);
console.log(`addLayerWithType(${url})`, fetch);
response = await fetch(url);
geojson = await response.json();
console.log(geojson);
layerOption.source = await ol.source.Vector.fromGeoJSON(geojson);
} else if (
typeof args[0] === "object" && //layer option
typeof args[1] === "object"
) {
//map object
layerOption = args[0];
} else {
reject();
}
break;
case 1: //layer option
default:
layerOption = args[0];
}
if (layerOption.labelStyleFunction) {
const layergroup = new ol.layer.Group({
layers: [
new LayerType({
...layerOption,
source: layerOption.source || new ol.source.Vector(),
style: layerOption.style || ol.style.flat.createDefaultStyle(),
}),
new ol.layer.Vector({
name: layerOption.name ? `${layerOption.name}_label` : "label",
source: layerOption.source || new ol.source.Vector(),
style: layerOption.labelStyleFunction,
declutter: true,
zIndex: layerOption.zIndex + 1,
}),
],
});
this.addLayer(layergroup);
resolve(layergroup);
} else {
const layer = new LayerType({
...layerOption,
source: layerOption.source || new ol.source.Vector(),
style: layerOption.style || ol.style.flat.createDefaultStyle(),
});
this.addLayer(layer);
resolve(layer);
}
} catch {
reject();
}
});
};
/**
* @author khy
* @description WebGL벡터레이어나 일반벡터레이어를 생성하여 map객체에 addLayer()함
* 전달인자를 1개, 2개, 3개를 줄 수 있음.
* 전달인자 1개: layer option
* 전달인자 2개: fetch url, layer option
* 전달인자 3개: fetch url, fetch option, layer option
* @param ...args
* case (args0) Layer option
* case (args0, args1) fetch url, layer option || layer option, map target
* case (args0, args1, args2) fetch url, fetch option, layer option
*/
/**
* @returns
* ol.layer.vector || WebGLVectorLayer
*/
ol.Map.prototype.addVectorLayer = async function (...args) {
return new Promise(async (resolve) => {
resolve(this.addLayerWithType(VectorLayer, args));
});
};
ol.Map.prototype.addRailLayer = async function (...args) {
return new Promise(async (resolve) => {
resolve(this.addLayerWithType(ol.layer.Vector, args));
});
};
/**
* @returns
* ol.layer.vector || ol.layer.WebGLPoints
*/
ol.Map.prototype.addPointsLayer = async function (...args) {
return new Promise(async (resolve) => {
resolve(this.addLayerWithType(PointsLayer, args));
});
};
ol.Map.prototype.addNormalRailLayer = function (options) {
return this.addVectorLayer(
"resource/NormalLine.json",
Object.assign(
{
name: "NormalLine",
style: {
"stroke-width": 2,
},
zIndex: 2,
},
options
)
);
};
ol.Map.prototype.addNormalStationLayer = function (options) {
return this.addPointsLayer(
"resource/NormalStation.json",
Object.assign(
{
name: "NormalStation",
style: {
"circle-radius": 5,
"circle-stroke-width": 3,
"circle-fill-color": "gainsboro",
},
zIndex: 3,
},
options
)
);
};
ol.Map.prototype.addKTXRailLayer = function (options) {
return this.addVectorLayer(
"resource/KTXLine.json",
Object.assign(
{
name: "KTXLine",
style: {
"stroke-color": "#005BAC",
"stroke-width": 5,
},
zIndex: 1,
},
options
)
);
};
ol.Map.prototype.addKTXStationLayer = function (options) {
return this.addPointsLayer(
"resource/KTXStation.json",
Object.assign(
{
name: "KTXStation",
style: {
"circle-radius": 7,
"circle-stroke-color": "#005BAC",
"circle-stroke-width": 3,
"circle-fill-color": "gainsboro",
},
zIndex: 4,
},
options
)
);
};
ol.Map.prototype.addBaseRailLayer = function () {
this.addNormalRailLayer();
this.addKTXRailLayer();
};
ol.Map.prototype.addBaseStationLayer = function () {
this.addNormalStationLayer();
this.addKTXStationLayer();
};
/*
function removeLayer(layername) {
getLayers(layername).forEach((layer) => {
layer.dispose();
map.removeLayer(layer);
})
}
*/
ol.Map.prototype.getLayer = function (layername) {
return this.getLayers(layername)[0];
};
ol.Map.prototype.getLayers = function (layername) {
return layername ? this.getAllLayers().filter((layer) => layer.get("name") === layername) : this.getLayerGroup().getLayers();
};
ol.Map.prototype.flyTo = function (params) {
this.once("postrender", () => {
this.flyToNow(params);
});
};
ol.Map.prototype.flyToNow = function (params) {
const convertedCenter = params.center.toEPSG3857();
const option = Object.assign(params, {
center: convertedCenter,
duration: 700,
easing: ol.easing.easeOut,
});
this.getView().animate(option);
};
ol.Map.prototype.setVisibleLayer = function (layername, visible) {
this.getLayers(layername).forEach((layer) => {
layer.setVisible(visible);
});
};
/**
* Overlay
* author: khy
* date: 23.09.07
* */
ol.Map.prototype.createOverlay = function () {
if (!document.getElementById("popup")) {
const popupElement = document.createElement("div");
popupElement.id = "popup";
this.getTargetElement().appendChild(popupElement);
}
const popup = new ol.Overlay({
id: "popup",
element: document.getElementById("popup"),
autoPan: true
});
this.addOverlay(popup);
return popup;
};
ol.Map.prototype.getOverlay = function () {
const popup = this.getOverlayById("popup") || this.createOverlay();
return popup;
};
ol.Map.prototype.showOverlay = function () {
this.getOverlay().element.style.display = null;
};
ol.Map.prototype.hideOverlay = function () {
this.getOverlay().element.style.display = "none";
};
ol.Map.prototype.isShowingOverlay = function () {
return (map.getOverlay().element.style.display !== "none")
}
/**
* Drag Interaction
* author: khy
* param
* options : {
* layerFilter: function(default=undefined),
* hitTolerance: number(default=0),
* checkWrapped: boolean(default=true),
* }
* usage
* map.addInteraction(new Drag( options | null ));
*
*/
class Drag extends ol.interaction.Pointer {
constructor(options) {
super({
handleDownEvent: handleDownEvent,
handleDragEvent: handleDragEvent,
handleMoveEvent: handleMoveEvent,
handleUpEvent: handleUpEvent,
});
this.options_ = options;
this.coordinate_ = null;
this.cursor_ = "grabbing";
this.feature_ = null;
this.previousCursor_ = undefined;
}
}
function handleDownEvent(evt) {
const map = evt.map;
const feature = map.forEachFeatureAtPixel(evt.pixel, (feature) => feature, this.options_);
if (feature) {
this.coordinate_ = evt.coordinate;
this.feature_ = feature;
}
return !!feature;
}
function handleDragEvent(evt) {
const deltaX = evt.coordinate[0] - this.coordinate_[0];
const deltaY = evt.coordinate[1] - this.coordinate_[1];
const geometry = this.feature_.getGeometry();
geometry.translate(deltaX, deltaY);
this.coordinate_[0] = evt.coordinate[0];
this.coordinate_[1] = evt.coordinate[1];
}
function handleMoveEvent(evt) {
if (this.cursor_) {
const map = evt.map;
const feature = map.forEachFeatureAtPixel(evt.pixel, (feature) => feature, this.options_);
const element = evt.map.getTargetElement();
if (feature) {
if (element.style.cursor != this.cursor_) {
this.previousCursor_ = element.style.cursor;
element.style.cursor = this.cursor_;
}
} else if (this.previousCursor_ !== undefined) {
element.style.cursor = this.previousCursor_;
this.previousCursor_ = undefined;
}
}
}
function handleUpEvent() {
this.coordinate_ = null;
this.feature_ = null;
return false;
}
/**
* loading spinner
* author: khy
* start
* mapLoadStart();
* end
* mapLoadEnd();
*/
ol.Map.prototype.addLoadingEffect = function (option) {
const css = `
@keyframes spinner {
to {
transform: rotate(360deg);
}
}
#map {
background: linear-gradient(to top left, #005bac, #3a3a3f);
}
${option?.transparent ? "/*" : ""}
#map > .ol-viewport {
display: none;
}
${option?.transparent ? "*/" : ""}
${option?.background ? "" : "/*"}
#map.spinner::before {
content: "";
position: absolute;
width: 100%;
height: 100%;
background: ${option?.background};
z-index:8;
}
${option?.background ? "" : "*/"}
#map.spinner::after {
content: "";
box-sizing: border-box;
position: absolute;
top: 50%;
left: 50%;
width: 40px;
height: 40px;
margin-top: -20px;
margin-left: -20px;
border-radius: 50%;
border: 4px solid ${option?.color ? option.color : "white"};
border-top-color: transparent;
animation: spinner 0.6s linear infinite;
z-index:9;
}
`;
const head = document.head || document.getElementsByTagName("head")[0];
if (head.querySelector("style[usage=spinner]")) {
return;
}
const style = document.createElement("style");
style.setAttribute("usage", "spinner");
style.type = "text/css";
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
head.appendChild(style);
};
ol.Map.prototype.removeLoadingEffect = function () {
const head = document.head || document.getElementsByTagName("head")[0];
head.querySelector("style[usage=spinner]")?.remove();
};
ol.Map.prototype.startLoadingEffect = function (option) {
this.addLoadingEffect(option);
this.getTargetElement().classList.add("spinner");
};
ol.Map.prototype.finishLoadingEffect = function () {
this.removeLoadingEffect();
this.getTargetElement().classList.remove("spinner");
};