mirror of
https://github.com/morgan9e/KorailMap
synced 2026-04-13 16:04:07 +09:00
init
This commit is contained in:
289
index.html
Normal file
289
index.html
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Korail GIS</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||||
|
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||||
|
crossorigin=""/>
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||||
|
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
||||||
|
crossorigin=""></script>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<style>
|
||||||
|
#map {
|
||||||
|
height: calc(100vh - 60px);
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.station-label {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 15px;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.train-marker {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottombar {
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottombar .left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottombar .left input[type="checkbox"] {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottombar .right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottombar .loading {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reloadTrains {
|
||||||
|
padding: 5px 10px;
|
||||||
|
background-color: #007BFF;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reloadTrains:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map"></div>
|
||||||
|
<div class="bottombar">
|
||||||
|
<div class="left">
|
||||||
|
<input type="checkbox" id="layer0"> Express
|
||||||
|
<input type="checkbox" id="layer1"> Semi
|
||||||
|
<input type="checkbox" id="layer2"> Normal
|
||||||
|
<input type="checkbox" id="layer3"> Passenger
|
||||||
|
<input type="checkbox" id="layer4"> Subway
|
||||||
|
<input type="checkbox" id="layer5"> Logis
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<span class="loading" id="loading">Loading...</span>
|
||||||
|
<button id="reloadTrains">Reload</button>
|
||||||
|
<input type="checkbox" id="autoReload"> Auto
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
async function myFetch(url, options = {}) {
|
||||||
|
let headers = options.headers
|
||||||
|
let f = fetch('https://proxy.devpg.net', {
|
||||||
|
headers: {
|
||||||
|
'X-Proxy-URL': url,
|
||||||
|
'X-Proxy-Header': JSON.stringify(headers),
|
||||||
|
'X-Proxy-Cache': '0'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(`fetch(${url}, headers=${JSON.stringify(headers)}`, f);
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
const trainAPI = 'https://gis.korail.com/api/train?bbox=120.6263671875,28.07910949377748,134.0736328125,45.094739803960664'
|
||||||
|
|
||||||
|
const server = ''
|
||||||
|
const map = L.map('map').setView([36.5, 128], 7.5);
|
||||||
|
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
const railLayers = {
|
||||||
|
express: L.layerGroup().addTo(map),
|
||||||
|
normal: L.layerGroup().addTo(map),
|
||||||
|
semi: L.layerGroup().addTo(map),
|
||||||
|
logis: L.layerGroup().addTo(map)
|
||||||
|
};
|
||||||
|
|
||||||
|
const stationLayers = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 64; i++) {
|
||||||
|
const layerGroup = L.layerGroup().addTo(map);
|
||||||
|
stationLayers.push(layerGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let layer of stationLayers) {
|
||||||
|
map.removeLayer(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const trainLayer = L.layerGroup().addTo(map);
|
||||||
|
|
||||||
|
function startSpinner() {
|
||||||
|
document.getElementById("loading").classList.remove("hidden");
|
||||||
|
}
|
||||||
|
function clearSpinner() {
|
||||||
|
document.getElementById("loading").classList.add("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadRail(type) {
|
||||||
|
fetch(`${server}/api/rail/${type}`)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Network response was not ok ${response.statusText}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
L.geoJSON(data, {
|
||||||
|
style: {
|
||||||
|
color: type === 'express' ? 'blue' : type === 'normal' ? 'green' : type === 'semi' ? 'orange' : 'red'
|
||||||
|
}
|
||||||
|
}).addTo(railLayers[type]);
|
||||||
|
})
|
||||||
|
.catch(error => console.error(`Error fetching ${type} rail data: `, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadStations() {
|
||||||
|
fetch(`${server}/api/station`)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
L.geoJSON(data, {
|
||||||
|
pointToLayer: function (feature, latlng) {
|
||||||
|
const shownLayer = parseInt(feature.properties.shown_layer.slice(2), 2);
|
||||||
|
|
||||||
|
const marker = L.marker(latlng, {
|
||||||
|
icon: L.divIcon({
|
||||||
|
className: 'station-label',
|
||||||
|
html: `<div>${feature.properties.name}</div>`,
|
||||||
|
iconSize: [100, 0]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (stationLayers[shownLayer]) {
|
||||||
|
stationLayers[shownLayer].addLayer(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
return marker;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error fetching station data: ', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadTrains() {
|
||||||
|
startSpinner();
|
||||||
|
myFetch(trainAPI, {
|
||||||
|
headers: {
|
||||||
|
"x-requested-with": "com.korail.talk",
|
||||||
|
"referer": "https://gis.korail.com/korailTalk/entrance",
|
||||||
|
"user-agent": "korailtalk AppVersion/6.3.3"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
trainLayer.clearLayers();
|
||||||
|
|
||||||
|
L.geoJSON(data, {
|
||||||
|
pointToLayer: function (feature, latlng) {
|
||||||
|
const trainIconHtml = `
|
||||||
|
<div style="position: relative; text-align: center;">
|
||||||
|
<div style="background-color: #007bff;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: inline-block;
|
||||||
|
border: 2px solid white;"></div>
|
||||||
|
<div style="font-size: 12px;
|
||||||
|
color: black;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid black;
|
||||||
|
margin-top: 2px;
|
||||||
|
padding: 1px 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
white-space: nowrap;">${feature.properties.trn_case} ${feature.properties.trn_no}</div>
|
||||||
|
</div>`;
|
||||||
|
return L.marker(latlng, {
|
||||||
|
icon: L.divIcon({
|
||||||
|
className: 'train-marker',
|
||||||
|
html: trainIconHtml,
|
||||||
|
iconSize: [30, 42],
|
||||||
|
iconAnchor: [15, 21]
|
||||||
|
})
|
||||||
|
}).bindPopup(`${feature.properties.trn_case} ${feature.properties.trn_no}<br>
|
||||||
|
(${feature.properties.dpt_stn_nm} -> ${feature.properties.arv_stn_nm})<br>
|
||||||
|
${feature.properties.now_stn} ${feature.properties.next_stn}`);
|
||||||
|
}
|
||||||
|
}).addTo(trainLayer);
|
||||||
|
clearSpinner();
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error fetching train data: ', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRail('express');
|
||||||
|
loadRail('normal');
|
||||||
|
loadRail('semi');
|
||||||
|
loadRail('logis');
|
||||||
|
|
||||||
|
|
||||||
|
for (let f = 0; f < 6; f++) {
|
||||||
|
document.getElementById(`layer${f}`).addEventListener('change', function () { // lable0: 0b 1XXXXX, label1: 0bX1XXXX, ...
|
||||||
|
for (let i = 0; i < 64; i++) {
|
||||||
|
if ( i & (1 << (5 - 0)) ) {
|
||||||
|
if (this.checked) {
|
||||||
|
map.addLayer(stationLayers[i]);
|
||||||
|
} else {
|
||||||
|
map.removeLayer(stationLayers[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('reloadTrains').addEventListener('click', loadTrains);
|
||||||
|
document.getElementById('autoReload').addEventListener('click', autoReload);
|
||||||
|
|
||||||
|
let reloadInterval = null
|
||||||
|
function autoReload() {
|
||||||
|
if (this.checked) {
|
||||||
|
reloadInterval = setInterval(loadTrains, 2000);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (reloadInterval) {
|
||||||
|
clearInterval(reloadInterval);
|
||||||
|
reloadInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadStations();
|
||||||
|
loadTrains();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1
json/rail_express.json
Normal file
1
json/rail_express.json
Normal file
File diff suppressed because one or more lines are too long
1
json/rail_logis.json
Normal file
1
json/rail_logis.json
Normal file
File diff suppressed because one or more lines are too long
1
json/rail_normal.json
Normal file
1
json/rail_normal.json
Normal file
File diff suppressed because one or more lines are too long
1
json/rail_semi.json
Normal file
1
json/rail_semi.json
Normal file
File diff suppressed because one or more lines are too long
15544
json/station.json
Normal file
15544
json/station.json
Normal file
File diff suppressed because it is too large
Load Diff
1
json/station.old.json
Normal file
1
json/station.old.json
Normal file
File diff suppressed because one or more lines are too long
5025
json/trains.json
Normal file
5025
json/trains.json
Normal file
File diff suppressed because it is too large
Load Diff
50
main.py
Normal file
50
main.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi import Request, Response
|
||||||
|
from fastapi.responses import JSONResponse, FileResponse
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
return FileResponse("index.html")
|
||||||
|
|
||||||
|
@app.get("/api/station")
|
||||||
|
async def getApiStation():
|
||||||
|
with open("json/station.json", "r") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
@app.get("/api/rail/{type}")
|
||||||
|
async def getApiRail(type: str):
|
||||||
|
with open(f"json/rail_{type}.json", "r") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/train")
|
||||||
|
async def getApiTrain():
|
||||||
|
tapi = requests.get('https://gis.korail.com/api/train?bbox=120.6263671875,28.07910949377748,134.0736328125,45.094739803960664',
|
||||||
|
headers = {
|
||||||
|
"x-requested-with": "com.korail.talk",
|
||||||
|
"referer": "https://gis.korail.com/korailTalk/entrance",
|
||||||
|
"user-agent": "korailtalk AppVersion/6.3.3"
|
||||||
|
})
|
||||||
|
|
||||||
|
return tapi.json()
|
||||||
|
|
||||||
|
with open("json/trains.json", "r") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__=="__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app)
|
||||||
30
static/gis.html
Normal file
30
static/gis.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>열차위치</title>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" >
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||||
|
<link rel="shortcut icon" href="https://gis.korail.com/korailTalk//favicon.ico">
|
||||||
|
<script src='https://gis.korail.com/korailTalk/libs/babel/babel.min.js'></script>
|
||||||
|
<script src='https://gis.korail.com/korailTalk/libs/geojson/geojson.min.js'></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="ol-helper.css" />
|
||||||
|
<link rel="stylesheet" href="ol.css" />
|
||||||
|
<script src='ol.js'></script>
|
||||||
|
<script>
|
||||||
|
const params = {
|
||||||
|
lon : null,
|
||||||
|
lat : null,
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id='map' style="position: absolute; width: 100%; height: 100%">
|
||||||
|
<div id="popup" class="ol-popup"></div>
|
||||||
|
</div>
|
||||||
|
<script src='ol-helper.js'></script>
|
||||||
|
<script src='main.js'></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
662
static/main.js
Normal file
662
static/main.js
Normal file
@@ -0,0 +1,662 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", async function () {
|
||||||
|
initMap();
|
||||||
|
await initLayer();
|
||||||
|
initView();
|
||||||
|
// initMapLegend();
|
||||||
|
mouseEvent();
|
||||||
|
});
|
||||||
|
|
||||||
|
let map;
|
||||||
|
|
||||||
|
let originalFetch = window.fetch;
|
||||||
|
async function newFetch(url, options = {}) {
|
||||||
|
let headers = options.headers ||
|
||||||
|
{
|
||||||
|
'user-agent': "Mozilla/5.0 (Linux; Android 12; SM-N976N Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.133 Mobile Safari/537.36 korailtalk AppVersion/6.3.3",
|
||||||
|
'x-requested-with': "com.korail.talk",
|
||||||
|
'sec-ch-ua-platform': "Android",
|
||||||
|
"referer": "https://gis.korail.com/korailTalk/entrance"
|
||||||
|
};
|
||||||
|
let f = originalFetch('https://proxy.devpg.net', {
|
||||||
|
headers: {
|
||||||
|
'X-Proxy-URL': 'https://gis.korail.com' + url,
|
||||||
|
'X-Proxy-Header': JSON.stringify(headers)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(`fetch(https://gis.korail.com${url}, headers=${JSON.stringify(headers)}`, f);
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
window.fetch = newFetch;
|
||||||
|
|
||||||
|
function initMap() {
|
||||||
|
map = new ol.Map({
|
||||||
|
target: 'map',
|
||||||
|
view: new ol.View({
|
||||||
|
minZoom: 5,
|
||||||
|
maxZoom: 15.9,
|
||||||
|
extent: [120, 30, 135, 45].toEPSG3857(),
|
||||||
|
}),
|
||||||
|
controls: ol.control.defaults.defaults({
|
||||||
|
attribution: false,
|
||||||
|
zoom: false,
|
||||||
|
rotate: false,
|
||||||
|
}),
|
||||||
|
interactions: ol.interaction.defaults.defaults({
|
||||||
|
altShiftDragRotate: false,
|
||||||
|
pinchRotate: false,
|
||||||
|
}).extend([new ol.interaction.DblClickDragZoom()])
|
||||||
|
});
|
||||||
|
if (params.lon && params.lat) {
|
||||||
|
map.getView().setCenter([params.lon, params.lat].toEPSG3857());
|
||||||
|
map.getView().setZoom(11);
|
||||||
|
} else {
|
||||||
|
map.getView().setCenter([127.35, 36.50].toEPSG3857());
|
||||||
|
map.getView().setZoom(6);
|
||||||
|
}
|
||||||
|
map.getTargetElement().style.background = '#b5dbf3';
|
||||||
|
}
|
||||||
|
|
||||||
|
const trainColors = {
|
||||||
|
ktx: '#1B4298',
|
||||||
|
itx: '#C10230',
|
||||||
|
etc: '#54565A',
|
||||||
|
srt:'#651C5C'
|
||||||
|
}
|
||||||
|
Object.freeze(trainColors);
|
||||||
|
|
||||||
|
const layerStyles = {
|
||||||
|
line: {
|
||||||
|
'stroke-width': 2.25,
|
||||||
|
'stroke-color': '#68a7d5',
|
||||||
|
},
|
||||||
|
station: {
|
||||||
|
'circle-radius': [
|
||||||
|
'match', ['get', 'grade', 'string'],
|
||||||
|
'0', ["interpolate", ["linear"],["zoom"], 5, 3, 15, 6],
|
||||||
|
'1', ["interpolate", ["linear"],["zoom"], 5, 3, 15, 6],
|
||||||
|
'2', ["interpolate", ["linear"],["zoom"], 5, 2, 15, 5],
|
||||||
|
'3', ["interpolate", ["linear"],["zoom"], 5, 2, 15, 5],
|
||||||
|
0
|
||||||
|
],
|
||||||
|
'circle-stroke-width': [
|
||||||
|
'match', ['get', 'grade', 'string'],
|
||||||
|
'0', ["interpolate", ["linear"],["zoom"], 5, 1, 15, 3],
|
||||||
|
'1', ["interpolate", ["linear"],["zoom"], 5, 1, 15, 3],
|
||||||
|
'2', ["interpolate", ["linear"],["zoom"], 5, 1, 15, 3],
|
||||||
|
'3', 0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
'circle-stroke-color': '#68a7d5',
|
||||||
|
'circle-fill-color': [
|
||||||
|
'match', ['get', 'grade', 'string'],
|
||||||
|
'3', '#68a7d5',
|
||||||
|
'white'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
station_label: {
|
||||||
|
'text-value': ['get', 'name', 'string'],
|
||||||
|
'text-stroke-color': "#68a7d5",
|
||||||
|
'text-stroke-width': 3,
|
||||||
|
'text-fill-color': "#ffffff",
|
||||||
|
'text-font': [
|
||||||
|
'match', ['get', 'grade', 'number'],
|
||||||
|
0, '17px SUIT-M',
|
||||||
|
1, '15px SUIT-M',
|
||||||
|
2, '13px SUIT-M',
|
||||||
|
3, '11px SUIT-M',
|
||||||
|
'0px SUIT-M'
|
||||||
|
],
|
||||||
|
'text-offset-x': ['get', 'text_offset_x'],
|
||||||
|
'text-offset-y': ['get', 'text_offset_y'],
|
||||||
|
},
|
||||||
|
trainStyle: (feature, resolution) => {
|
||||||
|
const {
|
||||||
|
trn_no: trnNo,
|
||||||
|
trn_case: trnCase,
|
||||||
|
trn_opr_cd: trnOpr,
|
||||||
|
trn_clsf: trnClass,
|
||||||
|
icon,
|
||||||
|
bearing
|
||||||
|
} = feature.getProperties();
|
||||||
|
const coords = feature.getGeometry().getCoordinates()
|
||||||
|
const overlabs = map.getLayer('train').getSource().getFeaturesAtCoordinate(coords).sort((a, b) => a.get('trn_no') - b.get('trn_no'));
|
||||||
|
|
||||||
|
return new ol.style.Style({
|
||||||
|
image: new ol.style.Icon({
|
||||||
|
src: `icon/train/ic_${icon}.svg`,
|
||||||
|
rotation: bearing,
|
||||||
|
scale: 0.6 + ((map.getView().getZoomForResolution(resolution) - 10) / 10),
|
||||||
|
declutterMode: 'none',
|
||||||
|
}),
|
||||||
|
text: (map.getView().getZoom() >= 11) ? new ol.style.Text({
|
||||||
|
font: '14px SUIT-B',
|
||||||
|
stroke: new ol.style.Stroke({
|
||||||
|
color: '#f3f3f3',
|
||||||
|
width: 1,
|
||||||
|
}),
|
||||||
|
fill: new ol.style.Fill({
|
||||||
|
color: trainColors[trnClass],
|
||||||
|
}),
|
||||||
|
offsetX: 5 + map.getView().getZoom(),
|
||||||
|
offsetY: 0 + overlabs.indexOf(feature) * 20,
|
||||||
|
overflow: true,
|
||||||
|
declutterMode: 'none',
|
||||||
|
textAlign: 'left',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: `${trnCase} ${trnNo}`,
|
||||||
|
}) : null,
|
||||||
|
zIndex: map.get('trainClicked')?.getId() == feature.getId() ? Infinity :
|
||||||
|
trnOpr == 15 ? overlabs.reverse().indexOf(feature) :
|
||||||
|
0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Object.freeze(layerStyles);
|
||||||
|
|
||||||
|
async function initLayer() {
|
||||||
|
try {
|
||||||
|
map.startLoadingEffect();
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
map.addBaseTileLayer('korean'),
|
||||||
|
|
||||||
|
initRailLayers(),
|
||||||
|
initStationLayers(),
|
||||||
|
|
||||||
|
initTrainLayer(),
|
||||||
|
]);
|
||||||
|
} catch {
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
map.finishLoadingEffect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initRailLayers() {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
await Promise.all([
|
||||||
|
// 고속선 정보
|
||||||
|
map.addRailLayer('/api/data/rail_ktx', {
|
||||||
|
name: 'rail_ktx',
|
||||||
|
style: layerStyles.line,
|
||||||
|
zIndex: 1,
|
||||||
|
}),
|
||||||
|
// 준고속선 정보
|
||||||
|
map.addRailLayer('/api/data/rail_semi', {
|
||||||
|
name: 'rail_semi',
|
||||||
|
style: layerStyles.line,
|
||||||
|
zIndex: 1,
|
||||||
|
}),
|
||||||
|
// 일반선 정보
|
||||||
|
map.addRailLayer('/api/data/rail_normal', {
|
||||||
|
name: 'rail_normal',
|
||||||
|
style: layerStyles.line,
|
||||||
|
zIndex: 1,
|
||||||
|
}),
|
||||||
|
// 물류선(추가) 정보
|
||||||
|
map.addRailLayer('/api/data/rail_logis', {
|
||||||
|
name: 'rail_logis',
|
||||||
|
style: layerStyles.line,
|
||||||
|
zIndex: 1,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initStationLayers() {
|
||||||
|
const response = await fetch('/api/data/station');
|
||||||
|
map.set('korailStation', Object.freeze(await response.json()));
|
||||||
|
|
||||||
|
const filteredStation = (binary) => {
|
||||||
|
const temp = JSON.parse(JSON.stringify(map.get('korailStation')));
|
||||||
|
temp.features = temp.features.filter((f) => (f.properties.text_offset_x && f.properties.text_offset_y) !== null);
|
||||||
|
temp.features = temp.features.filter((f) => Number(f.properties.shown_layer) & binary);
|
||||||
|
return ol.source.Vector.fromGeoJSON(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
await Promise.all([
|
||||||
|
// 고속역 정보
|
||||||
|
map.addPointsLayer({
|
||||||
|
name: 'KTX_Station',
|
||||||
|
source: filteredStation(0b100000),
|
||||||
|
style: layerStyles.station,
|
||||||
|
labelStyleFunction: layerStyles.station_label,
|
||||||
|
zIndex: 2,
|
||||||
|
}),
|
||||||
|
// 준고속역 정보
|
||||||
|
map.addPointsLayer({
|
||||||
|
name: 'SemiExpress_Station',
|
||||||
|
source: filteredStation(0b010000),
|
||||||
|
style: layerStyles.station,
|
||||||
|
labelStyleFunction: layerStyles.station_label,
|
||||||
|
zIndex: 2,
|
||||||
|
}),
|
||||||
|
// 일반역 정보
|
||||||
|
map.addPointsLayer({
|
||||||
|
name: 'Normal_Station',
|
||||||
|
source: filteredStation(0b001000),
|
||||||
|
style: layerStyles.station,
|
||||||
|
labelStyleFunction: layerStyles.station_label,
|
||||||
|
zIndex: 2,
|
||||||
|
}),
|
||||||
|
// 여객역 정보
|
||||||
|
map.addPointsLayer({
|
||||||
|
name: 'Passenger_Station',
|
||||||
|
source: filteredStation(0b000100),
|
||||||
|
style: layerStyles.station,
|
||||||
|
labelStyleFunction: layerStyles.station_label,
|
||||||
|
zIndex: 2,
|
||||||
|
}),
|
||||||
|
// 물류역 정보
|
||||||
|
map.addPointsLayer({
|
||||||
|
name: 'Dist_Station',
|
||||||
|
source: filteredStation(0b000001),
|
||||||
|
style: layerStyles.station,
|
||||||
|
labelStyleFunction: layerStyles.station_label,
|
||||||
|
zIndex: 2,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initRefreshDOM() {
|
||||||
|
const dom = document.createElement('div')
|
||||||
|
dom.id = 'refresh-info'
|
||||||
|
dom.style.position = 'absolute'
|
||||||
|
dom.style.bottom = '0px'
|
||||||
|
dom.style.right = '0px'
|
||||||
|
dom.style.display = 'flex'
|
||||||
|
dom.style.flexDirection = 'column'
|
||||||
|
dom.style.justifyContent = 'space-evenly'
|
||||||
|
dom.style.alignItems = 'end'
|
||||||
|
dom.style.margin = '20px'
|
||||||
|
dom.style.padding = 'env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)'
|
||||||
|
dom.style.boxSizing = 'border-box'
|
||||||
|
dom.style.touchAction = 'none'
|
||||||
|
|
||||||
|
const button = document.createElement('input')
|
||||||
|
button.type = 'image'
|
||||||
|
button.src = 'icon/refresh.svg'
|
||||||
|
button.alt = '새로고침'
|
||||||
|
button.onclick = (event) => {
|
||||||
|
refreshTrainLayer()
|
||||||
|
|
||||||
|
const target = event.currentTarget
|
||||||
|
target.classList.add('rotate')
|
||||||
|
setTimeout(() => {
|
||||||
|
target.classList.remove('rotate');
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
button.style.width = '1rem'
|
||||||
|
button.style.height = '1rem'
|
||||||
|
button.style.background = 'transparent'
|
||||||
|
button.style.border = '0px'
|
||||||
|
button.style.borderRadius = '50%'
|
||||||
|
button.style.boxShadow = '0px 0px 5px gainsboro'
|
||||||
|
button.style.background = 'white'
|
||||||
|
button.style.margin = '5px 0px'
|
||||||
|
button.style.padding = '5px'
|
||||||
|
|
||||||
|
const text = document.createElement('span')
|
||||||
|
text.style.fontSize = 'small'
|
||||||
|
text.style.transition = 'color 0.5s'
|
||||||
|
text.style.color = 'black'
|
||||||
|
|
||||||
|
dom.appendChild(button)
|
||||||
|
dom.appendChild(text)
|
||||||
|
map.getTargetElement().appendChild(dom)
|
||||||
|
|
||||||
|
updateRefreshText();
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshIterator = {
|
||||||
|
interval: undefined,
|
||||||
|
function: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncIterator() {
|
||||||
|
const zoom = map.getView().getZoom();
|
||||||
|
const intZoom = Math.floor(zoom);
|
||||||
|
const intervalSec = 60 - ((intZoom - 5) * 5.5);
|
||||||
|
|
||||||
|
refreshIterator.interval = intervalSec * 1000;
|
||||||
|
clearInterval(refreshIterator.function);
|
||||||
|
refreshIterator.function = setInterval(refreshTrainLayer, refreshIterator.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRefreshText() {
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0'); // 월은 0부터 시작하므로 +1 필요
|
||||||
|
const day = String(now.getDate()).padStart(2, '0');
|
||||||
|
const hours = String(now.getHours()).padStart(2, '0');
|
||||||
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||||
|
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||||
|
|
||||||
|
const text = document.querySelector('div#refresh-info > span')
|
||||||
|
text.textContent = `(${year}.${month}.${day}. ${hours}:${minutes}:${seconds}) 기준`;
|
||||||
|
|
||||||
|
text.classList.add('blink')
|
||||||
|
setTimeout(() => {
|
||||||
|
text.classList.remove('blink');
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshTrainLayer() {
|
||||||
|
const getBufferedExtent = (view) => {
|
||||||
|
const extent = view.calculateExtent();
|
||||||
|
const resolution = view.getResolution();
|
||||||
|
const buffer = 100 * resolution;
|
||||||
|
const bufferedExtent = [
|
||||||
|
extent[0] - buffer,
|
||||||
|
extent[1] - buffer,
|
||||||
|
extent[2] + buffer,
|
||||||
|
extent[3] + buffer,
|
||||||
|
]
|
||||||
|
|
||||||
|
return bufferedExtent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
const bufferedExtent = getBufferedExtent(map.getView());
|
||||||
|
const bbox = bufferedExtent.toWGS84().join(',');
|
||||||
|
const filter = `${params.trn ? `trnNo=${params.trn}` : `bbox=${bbox}`}`;
|
||||||
|
if (params.trn && params.date) {
|
||||||
|
const date = params.date
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
const yyyy = now.getFullYear()
|
||||||
|
const mm = (now.getMonth() +1).toString().padStart(2, '0')
|
||||||
|
const dd = now.getDate().toString().padStart(2, '0')
|
||||||
|
const yyyymmdd = `${yyyy}${mm}${dd}`;
|
||||||
|
|
||||||
|
if (date !== yyyymmdd) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const response = await fetch(`/api/train?${filter}`);
|
||||||
|
const geojson = await response.json();
|
||||||
|
|
||||||
|
const newFeatures = ol.source.Vector.fromGeoJSON(geojson).getFeatures();
|
||||||
|
if (params?.trn) {
|
||||||
|
newFeatures.forEach((f) => {
|
||||||
|
f.set('icon', 'train');
|
||||||
|
f.set('bearing', 0);
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
newFeatures.forEach((f) => {
|
||||||
|
f.set('icon', `${f.get('trn_clsf')}${(f.get('delay') > 20 ? '_delay' : '')}`);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const trainSource = map.getLayer('train')?.getSource();
|
||||||
|
|
||||||
|
trainSource?.clear();
|
||||||
|
trainSource?.addFeatures(newFeatures);
|
||||||
|
|
||||||
|
if (map.get('trainClicked') && map.isShowingOverlay()) {
|
||||||
|
const past = map.get('trainClicked');
|
||||||
|
const now = newFeatures.find((feature) => (feature.get('trn_no') === past.get('trn_no')));
|
||||||
|
if (now) {
|
||||||
|
const [pastX, pastY] = past.getGeometry().getCoordinates();
|
||||||
|
const nowCoords = now.getGeometry().getCoordinates();
|
||||||
|
const [nowX, nowY] = nowCoords;
|
||||||
|
if ( !(pastX === nowX && pastY === nowY) ) {
|
||||||
|
map.getLayer('train').once('postrender',() => {
|
||||||
|
map.getOverlay().setPosition(nowCoords);
|
||||||
|
map.getView().animate({
|
||||||
|
center: nowCoords,
|
||||||
|
duration: 500,
|
||||||
|
});
|
||||||
|
showPopup(now);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
map.hideOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//sendMessageToApp();
|
||||||
|
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
.then(updateRefreshText)
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadTrainLayer() {
|
||||||
|
refreshTrainLayer()
|
||||||
|
syncIterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initTrainLayer() {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
map.addPointsLayer({
|
||||||
|
name: 'train',
|
||||||
|
style: layerStyles.trainStyle,
|
||||||
|
zIndex: 9,
|
||||||
|
declutter: true,
|
||||||
|
});
|
||||||
|
map.on('pointerdrag', map.hideOverlay)
|
||||||
|
map.on('moveend', reloadTrainLayer)
|
||||||
|
initRefreshDOM();
|
||||||
|
resolve()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const showPopup = (feature) => {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
map.set('trainClicked', feature);
|
||||||
|
|
||||||
|
const coordinate = feature.getGeometry().getFlatCoordinates();
|
||||||
|
const popup = map.getOverlay();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { trn_no, trn_case, trn_clsf, dpt_stn_nm, dpt_pln_dttm, arv_stn_nm, arv_pln_dttm, now_stn, next_stn, delay } = feature.getProperties();
|
||||||
|
const dpt_time = `${dpt_pln_dttm.slice(8,10)}:${dpt_pln_dttm.slice(10,12)}`;
|
||||||
|
const arv_time = `${arv_pln_dttm.slice(8,10)}:${arv_pln_dttm.slice(10,12)}`;
|
||||||
|
const now_loc = now_stn && next_stn ? `${now_stn}${next_stn}` : '';
|
||||||
|
|
||||||
|
html = `
|
||||||
|
<div class="train-popup">
|
||||||
|
<div class="train-popup-name">
|
||||||
|
<span class="train-popup-no" style="color: #0066b3;">${trn_case} ${trn_no}</span>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
<div class="train-popup-info">
|
||||||
|
<div><title>운행구간</title><content>${dpt_stn_nm}(${dpt_time}) ~ ${arv_stn_nm}(${arv_time})</content></div>
|
||||||
|
<div><title>현재위치</title><content>${now_loc}</content></div>
|
||||||
|
<div usage="delay"><title>예상지연</title><content style="${(delay > 0) ? 'color: #159aff; font-weight: bold' : ''}">${delay} 분</content></div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
popup.element.innerHTML = html;
|
||||||
|
|
||||||
|
if (delay === null) {
|
||||||
|
popup.element.querySelector('div[usage="delay"]')?.remove()
|
||||||
|
popup.element.querySelector('div[usage="delay-reason"]')?.remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
popup.setPosition(coordinate);
|
||||||
|
map.getView().animate({
|
||||||
|
center: coordinate,
|
||||||
|
duration: 500,
|
||||||
|
})
|
||||||
|
map.showOverlay();
|
||||||
|
|
||||||
|
popup.element.style.top = `-${popup.element.offsetHeight + 20}px`;
|
||||||
|
popup.element.style.left = `-${popup.element.offsetWidth / 2}px`;
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
map.hideOverlay();
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseEvent() {
|
||||||
|
map.set('trainClicked', null);
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
layerFilter: (layer) => layer.get('name') === 'train',
|
||||||
|
hitTolerance: 20
|
||||||
|
};
|
||||||
|
|
||||||
|
map.on('singleclick', async (e) => {
|
||||||
|
if (map.getView().getInteracting() || map.getView().getAnimating()) {
|
||||||
|
map.getTargetElement().style.cursor = "grabbing";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (map.get('trainClicked') && map.getFeaturesAtPixel(e.pixel, options).length > 0 && map.get('trainClicked') === map.getFeaturesAtPixel(e.pixel, options)[0]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map.get('trainClicked') !== null) {
|
||||||
|
map.set('trainClicked', null);
|
||||||
|
map.hideOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
const features = map.getFeaturesAtPixel(e.pixel, options);
|
||||||
|
if (features.length > 0 && showPopup(features[0])) {
|
||||||
|
map.showOverlay();
|
||||||
|
} else {
|
||||||
|
map.hideOverlay();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLegend() {
|
||||||
|
const controllerDOM = document.createElement('div');
|
||||||
|
controllerDOM.id = 'viewController'
|
||||||
|
controllerDOM.style.position = 'absolute';
|
||||||
|
controllerDOM.style.width = '100%';
|
||||||
|
controllerDOM.style.bottom = '0px';
|
||||||
|
controllerDOM.style.minWidth = '100px';
|
||||||
|
controllerDOM.style.display = 'flex';
|
||||||
|
controllerDOM.style.flexDirection = 'row';
|
||||||
|
controllerDOM.style.justifyContent = 'space-evenly';
|
||||||
|
controllerDOM.style.marginBottom = '30px';
|
||||||
|
controllerDOM.style.touchAction = 'none';
|
||||||
|
|
||||||
|
const buttons = ["고속", "일반", "광역", "화물"]
|
||||||
|
|
||||||
|
buttons.forEach((name, index) => {
|
||||||
|
const buttonSize = '50px';
|
||||||
|
const button = document.createElement('div');
|
||||||
|
button.style.display = 'block';
|
||||||
|
button.style.justifyContent = 'space-between';
|
||||||
|
button.style.width = buttonSize;
|
||||||
|
button.style.height = buttonSize
|
||||||
|
button.style.lineHeight = buttonSize;
|
||||||
|
button.style.borderRadius = buttonSize;
|
||||||
|
button.style.background = 'darkgray';
|
||||||
|
button.style.color = 'white';
|
||||||
|
button.style.textAlign = 'center';
|
||||||
|
button.style.boxShadow = '1px 1px 5px gray';
|
||||||
|
button.textContent = name;
|
||||||
|
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
map.set('viewType', index);
|
||||||
|
viewcontrol(map.get('viewType'));
|
||||||
|
})
|
||||||
|
|
||||||
|
controllerDOM.appendChild(button);
|
||||||
|
});
|
||||||
|
|
||||||
|
return controllerDOM;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initView() {
|
||||||
|
const view = map.getView();
|
||||||
|
|
||||||
|
if (params?.trn && params?.date) {
|
||||||
|
map.once('rendercomplete', () => {
|
||||||
|
const trains = map.getLayer('train').getSource().getFeatures();
|
||||||
|
const myTrain = trains.find((t) => t.get('trn_no') == params.trn);
|
||||||
|
|
||||||
|
if (myTrain) {
|
||||||
|
showPopup(myTrain);
|
||||||
|
if (!(params.lon && params.lat)) {
|
||||||
|
view.setCenter(myTrain.getGeometry().flatCoordinates)
|
||||||
|
view.setZoom(11);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// createModalPopup('열차가 운행중이지 않습니다.');
|
||||||
|
setTimeout(() => window.location.reload(), 60000)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createModalPopup(_content) {
|
||||||
|
const backgroundDOM = document.createElement('div');
|
||||||
|
backgroundDOM.style.position = 'absolute';
|
||||||
|
backgroundDOM.style.display = 'flex';
|
||||||
|
backgroundDOM.style.width = '100%';
|
||||||
|
backgroundDOM.style.height = '100%';
|
||||||
|
backgroundDOM.style.background = 'rgba(0,0,0,0.5)';
|
||||||
|
backgroundDOM.style.userSelect = 'none';
|
||||||
|
backgroundDOM.id = 'modal-popup';
|
||||||
|
|
||||||
|
|
||||||
|
const popupDOM = document.createElement('div');
|
||||||
|
popupDOM.style.margin = 'auto';
|
||||||
|
popupDOM.style.padding = '10px';
|
||||||
|
popupDOM.style.borderRadius = '5px';
|
||||||
|
popupDOM.style.background = 'white';
|
||||||
|
popupDOM.style.textAlign = 'center';
|
||||||
|
//popupDOM.style.zIndex = 1;
|
||||||
|
popupDOM.innerText = _content;
|
||||||
|
|
||||||
|
backgroundDOM.appendChild(popupDOM);
|
||||||
|
document.body.appendChild(backgroundDOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
const webviewPlatform = Object.freeze({
|
||||||
|
IOS_WEBVIEW: "iOS WebView",
|
||||||
|
IOS_BROWSER: "iOS Browser",
|
||||||
|
AOS_WEBVIEW: "Android WebView",
|
||||||
|
AOS_BROWSER: "Android Browser",
|
||||||
|
UNKNOWN: "Unknown Platform"
|
||||||
|
});
|
||||||
|
|
||||||
|
function getWebViewPlatform() {
|
||||||
|
var userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
||||||
|
|
||||||
|
if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
|
||||||
|
if (window.webkit && window.webkit.messageHandlers) {
|
||||||
|
return webviewPlatform.IOS_WEBVIEW;
|
||||||
|
}
|
||||||
|
return webviewPlatform.IOS_BROWSER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/android/i.test(userAgent)) {
|
||||||
|
if (window.Android) {
|
||||||
|
return webviewPlatform.AOS_WEBVIEW;
|
||||||
|
}
|
||||||
|
return webviewPlatform.AOS_BROWSER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return webviewPlatform.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMessageToApp() {
|
||||||
|
const osPlatform = getWebViewPlatform()
|
||||||
|
const message = "gis refresh"
|
||||||
|
switch (osPlatform) {
|
||||||
|
case webviewPlatform.IOS_WEBVIEW :
|
||||||
|
case webviewPlatform.IOS_BROWSER :
|
||||||
|
window.webkit.messageHandlers.refresh.postMessage(message)
|
||||||
|
return;
|
||||||
|
case webviewPlatform.AOS_WEBVIEW :
|
||||||
|
case webviewPlatform.AOS_BROWSER :
|
||||||
|
Android.showMessage(message);
|
||||||
|
return;
|
||||||
|
default :
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
142
static/ol-helper.css
Normal file
142
static/ol-helper.css
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/*font*/
|
||||||
|
@font-face {
|
||||||
|
font-family: "SUIT-B";
|
||||||
|
src: url("SUIT-Regular.ttf");
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "SUIT-M";
|
||||||
|
src: url("SUIT-Regular.ttf");
|
||||||
|
font-weight: medium;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "SUIT-R";
|
||||||
|
src: url("SUIT-Regular.ttf");
|
||||||
|
font-weight: regular;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
font-family: SUIT-R !important;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
touch-action: pan-x;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin:0;
|
||||||
|
}
|
||||||
|
html:has(#map), body:has(#map) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#map {
|
||||||
|
background: #171933;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overlay-container {
|
||||||
|
position:absolute;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 5px 5px 3px rgba(0,0,0,0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
.ol-overlay-container:after {
|
||||||
|
top: 100%;
|
||||||
|
border: solid transparent;
|
||||||
|
content: " ";
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.ol-overlay-container:after {
|
||||||
|
border-top-color: #fff;
|
||||||
|
border-width: 10px;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.train-popup {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 15px 27px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
font-family: SUIT;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.train-popup-name {
|
||||||
|
justify-content: space-between;
|
||||||
|
display: flex;
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
.train-popup-name > .train-popup-no{
|
||||||
|
font-size: 25px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: normal;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
text-align: left;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
.train-popup-name > .train-popup-no {
|
||||||
|
color: #496dff;
|
||||||
|
}
|
||||||
|
.train-popup-pair {
|
||||||
|
color: #9b9b9b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.train-popup hr {
|
||||||
|
display: block;
|
||||||
|
margin: 8px 0px;
|
||||||
|
}
|
||||||
|
.train-popup-info {
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.train-popup-info > div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
.train-popup-info > div:not(:last-child) {
|
||||||
|
margin: 7px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.train-popup-info > div > title {
|
||||||
|
display: block;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
.train-popup-info > div > content {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
.train-popup-info > div > span {
|
||||||
|
font-size: x-small;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
0% { color: lightgray }
|
||||||
|
100% { color: black }
|
||||||
|
}
|
||||||
|
.blink {
|
||||||
|
animation: blink 0.5s
|
||||||
|
}
|
||||||
|
@keyframes rotate {
|
||||||
|
0% { transform: rotate(0deg) }
|
||||||
|
100% { transform: rotate(-360deg) }
|
||||||
|
}
|
||||||
|
.rotate {
|
||||||
|
animation: rotate 0.5s
|
||||||
|
}
|
||||||
772
static/ol-helper.js
Normal file
772
static/ol-helper.js
Normal 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");
|
||||||
|
};
|
||||||
350
static/ol.css
Normal file
350
static/ol.css
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
:root,
|
||||||
|
:host {
|
||||||
|
--ol-background-color: white;
|
||||||
|
--ol-accent-background-color: #F5F5F5;
|
||||||
|
--ol-subtle-background-color: rgba(128, 128, 128, 0.25);
|
||||||
|
--ol-partial-background-color: rgba(255, 255, 255, 0.75);
|
||||||
|
--ol-foreground-color: #333333;
|
||||||
|
--ol-subtle-foreground-color: #666666;
|
||||||
|
--ol-brand-color: #00AAFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-box {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1.5px solid var(--ol-background-color);
|
||||||
|
background-color: var(--ol-partial-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-mouse-position {
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-line {
|
||||||
|
background: var(--ol-partial-background-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
bottom: 8px;
|
||||||
|
left: 8px;
|
||||||
|
padding: 2px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-line-inner {
|
||||||
|
border: 1px solid var(--ol-subtle-foreground-color);
|
||||||
|
border-top: none;
|
||||||
|
color: var(--ol-foreground-color);
|
||||||
|
font-size: 10px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 1px;
|
||||||
|
will-change: contents, width;
|
||||||
|
transition: all 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-bar {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-bar-inner {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-step-marker {
|
||||||
|
width: 1px;
|
||||||
|
height: 15px;
|
||||||
|
background-color: var(--ol-foreground-color);
|
||||||
|
float: right;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-step-text {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -5px;
|
||||||
|
font-size: 10px;
|
||||||
|
z-index: 11;
|
||||||
|
color: var(--ol-foreground-color);
|
||||||
|
text-shadow: -1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-text {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
bottom: 25px;
|
||||||
|
color: var(--ol-foreground-color);
|
||||||
|
text-shadow: -1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-singlebar {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
z-index: 9;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid var(--ol-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-singlebar-even {
|
||||||
|
background-color: var(--ol-subtle-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-singlebar-odd {
|
||||||
|
background-color: var(--ol-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-unsupported {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-viewport,
|
||||||
|
.ol-unselectable {
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-viewport canvas {
|
||||||
|
all: unset;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-viewport {
|
||||||
|
touch-action: pan-x pan-y;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-selectable {
|
||||||
|
-webkit-touch-callout: default;
|
||||||
|
-webkit-user-select: text;
|
||||||
|
-moz-user-select: text;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-grabbing {
|
||||||
|
cursor: -webkit-grabbing;
|
||||||
|
cursor: -moz-grabbing;
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-grab {
|
||||||
|
cursor: move;
|
||||||
|
cursor: -webkit-grab;
|
||||||
|
cursor: -moz-grab;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-control {
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--ol-subtle-background-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoom {
|
||||||
|
top: .5em;
|
||||||
|
left: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-rotate {
|
||||||
|
top: .5em;
|
||||||
|
right: .5em;
|
||||||
|
transition: opacity .25s linear, visibility 0s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-rotate.ol-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity .25s linear, visibility 0s linear .25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoom-extent {
|
||||||
|
top: 4.643em;
|
||||||
|
left: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-full-screen {
|
||||||
|
right: .5em;
|
||||||
|
top: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-control button {
|
||||||
|
display: block;
|
||||||
|
margin: 1px;
|
||||||
|
padding: 0;
|
||||||
|
color: var(--ol-subtle-foreground-color);
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: inherit;
|
||||||
|
text-align: center;
|
||||||
|
height: 1.375em;
|
||||||
|
width: 1.375em;
|
||||||
|
line-height: .4em;
|
||||||
|
background-color: var(--ol-background-color);
|
||||||
|
border: none;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-control button::-moz-focus-inner {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoom-extent button {
|
||||||
|
line-height: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-compass {
|
||||||
|
display: block;
|
||||||
|
font-weight: normal;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-touch .ol-control button {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-touch .ol-zoom-extent {
|
||||||
|
top: 5.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-control button:hover,
|
||||||
|
.ol-control button:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
outline: 1px solid var(--ol-subtle-foreground-color);
|
||||||
|
color: var(--ol-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoom .ol-zoom-in {
|
||||||
|
border-radius: 2px 2px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoom .ol-zoom-out {
|
||||||
|
border-radius: 0 0 2px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution {
|
||||||
|
text-align: right;
|
||||||
|
bottom: .5em;
|
||||||
|
right: .5em;
|
||||||
|
max-width: calc(100% - 1.3em);
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row-reverse;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution a {
|
||||||
|
color: var(--ol-subtle-foreground-color);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 1px .5em;
|
||||||
|
color: var(--ol-foreground-color);
|
||||||
|
text-shadow: 0 0 2px var(--ol-background-color);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution li {
|
||||||
|
display: inline;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution li:not(:last-child):after {
|
||||||
|
content: " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution img {
|
||||||
|
max-height: 2em;
|
||||||
|
max-width: inherit;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution button {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution.ol-collapsed ul {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution:not(.ol-collapsed) {
|
||||||
|
background: var(--ol-partial-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution.ol-uncollapsible {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
border-radius: 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution.ol-uncollapsible img {
|
||||||
|
margin-top: -.2em;
|
||||||
|
max-height: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution.ol-uncollapsible button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoomslider {
|
||||||
|
top: 4.5em;
|
||||||
|
left: .5em;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoomslider button {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-touch .ol-zoomslider {
|
||||||
|
top: 5.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap {
|
||||||
|
left: 0.5em;
|
||||||
|
bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap.ol-uncollapsible {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 0 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap .ol-overviewmap-map,
|
||||||
|
.ol-overviewmap button {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap .ol-overviewmap-map {
|
||||||
|
border: 1px solid var(--ol-subtle-foreground-color);
|
||||||
|
height: 150px;
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap:not(.ol-collapsed) button {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap.ol-collapsed .ol-overviewmap-map,
|
||||||
|
.ol-overviewmap.ol-uncollapsible button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap:not(.ol-collapsed) {
|
||||||
|
background: var(--ol-subtle-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap-box {
|
||||||
|
border: 1.5px dotted var(--ol-subtle-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap .ol-overviewmap-box:hover {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
4
static/ol.js
Normal file
4
static/ol.js
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user