init
This commit is contained in:
commit
fb145c44f2
|
@ -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>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
|
@ -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>
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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");
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue