This commit is contained in:
2024-09-10 10:27:25 +09:00
commit fb145c44f2
15 changed files with 22873 additions and 0 deletions

772
static/ol-helper.js Normal file
View File

@@ -0,0 +1,772 @@
/**
* 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");
};