mirror of
https://github.com/Maks1mS/free-ozon-dpr.git
synced 2025-10-21 01:17:31 +03:00
chore: improve ux and dx (#1)
* move to src * hide popup at startup * add eslint
This commit is contained in:
34
src/features.js
Normal file
34
src/features.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import Feature from "ol/Feature";
|
||||
import Point from "ol/geom/Point";
|
||||
import { fromLonLat } from "ol/proj";
|
||||
import { Circle as CircleStyle, Fill, Stroke, Style } from "ol/style";
|
||||
|
||||
const iconStyle = new Style({
|
||||
image: new CircleStyle({
|
||||
radius: 8,
|
||||
fill: new Fill({
|
||||
color: "#c90036",
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: "#fff",
|
||||
width: 2,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
function createFeatures(places) {
|
||||
return places.map((place) => {
|
||||
const feature = new Feature({
|
||||
geometry: new Point(fromLonLat(place.coordinates)),
|
||||
name: `[${place.provider}] ${place.name}`,
|
||||
address: place.address,
|
||||
link: place.link,
|
||||
source: place.source,
|
||||
operationTime: place.operationTime,
|
||||
});
|
||||
feature.setStyle(iconStyle);
|
||||
return feature;
|
||||
});
|
||||
}
|
||||
|
||||
export { createFeatures };
|
48
src/geolocation.js
Normal file
48
src/geolocation.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import map, { view } from "./map";
|
||||
import Feature from "ol/Feature.js";
|
||||
import Geolocation from "ol/Geolocation.js";
|
||||
import Point from "ol/geom/Point.js";
|
||||
import { Circle as CircleStyle, Fill, Stroke, Style } from "ol/style.js";
|
||||
import { Vector as VectorSource } from "ol/source.js";
|
||||
import { Vector as VectorLayer } from "ol/layer.js";
|
||||
|
||||
const geolocation = new Geolocation({
|
||||
tracking: true,
|
||||
trackingOptions: {
|
||||
enableHighAccuracy: true,
|
||||
},
|
||||
projection: view.getProjection(),
|
||||
});
|
||||
|
||||
const positionFeature = new Feature();
|
||||
positionFeature.setStyle(
|
||||
new Style({
|
||||
image: new CircleStyle({
|
||||
radius: 6,
|
||||
fill: new Fill({
|
||||
color: "#3399CC",
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: "#fff",
|
||||
width: 2,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const accuracyFeature = new Feature();
|
||||
geolocation.on("change:accuracyGeometry", function () {
|
||||
accuracyFeature.setGeometry(geolocation.getAccuracyGeometry());
|
||||
});
|
||||
|
||||
geolocation.on("change:position", function () {
|
||||
const coordinates = geolocation.getPosition();
|
||||
positionFeature.setGeometry(coordinates ? new Point(coordinates) : null);
|
||||
});
|
||||
|
||||
new VectorLayer({
|
||||
map: map,
|
||||
source: new VectorSource({
|
||||
features: [accuracyFeature, positionFeature],
|
||||
}),
|
||||
});
|
39
src/index.html
Normal file
39
src/index.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
|
||||
<title>БЕСПЛАТНЫЕ ПВЗ ОЗОН В ДНР</title>
|
||||
<style>
|
||||
#popup {
|
||||
display: none;
|
||||
}
|
||||
.ol-overlay-container > #popup {
|
||||
display: unset;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map" class="map"></div>
|
||||
<div id="popup" class="ol-popup">
|
||||
<a href="#" id="popup-closer" class="ol-popup-closer">✖</a>
|
||||
<div id="popup-content">
|
||||
<p id="popup-name"></p>
|
||||
<a id="popup-address"></a>
|
||||
<p>График работы: <span id="popup-operation-time">неизвестно</span></p>
|
||||
<p>
|
||||
Отсканируйте QR или нажмите на него
|
||||
<a id="popup-link" href="">
|
||||
<canvas id="popup-canvas"></canvas>
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a id="popup-source">Источник</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
19
src/main.js
Normal file
19
src/main.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import "./style.css";
|
||||
|
||||
import map from "./map";
|
||||
|
||||
import "./popup";
|
||||
|
||||
import mergedData from "../merged-data.json";
|
||||
import { createFeatures } from "./features";
|
||||
import VectorSource from "ol/source/Vector";
|
||||
import VectorLayer from "ol/layer/Vector";
|
||||
|
||||
const features = createFeatures(mergedData);
|
||||
const vectorSource = new VectorSource({
|
||||
features,
|
||||
});
|
||||
const vectorLayer = new VectorLayer({
|
||||
source: vectorSource,
|
||||
});
|
||||
map.addLayer(vectorLayer);
|
38
src/map.js
Normal file
38
src/map.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'ol/ol.css';
|
||||
import Map from 'ol/Map';
|
||||
import View from 'ol/View';
|
||||
import TileLayer from 'ol/layer/Tile';
|
||||
import { fromLonLat } from 'ol/proj';
|
||||
import { XYZ } from 'ol/source';
|
||||
// import { createXYZ } from 'ol/tilegrid';
|
||||
|
||||
const MAP_TARGET = 'map';
|
||||
const MAP_CENTER = fromLonLat([37.57725139554275, 48.02287702854201]);
|
||||
const MAP_ZOOM = 8.5;
|
||||
|
||||
const customTileSource = new TileLayer({
|
||||
source: new XYZ({
|
||||
// url: 'https://tile-server.ozon.ru/tile/default/{z}/{x}/{y}.png'
|
||||
|
||||
tileUrlFunction: ([z, x, y]) => {
|
||||
const s = x % 4 + (y % 4) * 4
|
||||
return `https://i${s}.wikimapia.org/?x=${x}&y=${y}&zoom=${z}&type=map&lng=1`
|
||||
},
|
||||
maxZoom: 18,
|
||||
})
|
||||
});
|
||||
|
||||
export const view = new View({
|
||||
center: MAP_CENTER,
|
||||
zoom: MAP_ZOOM,
|
||||
})
|
||||
|
||||
const map = new Map({
|
||||
target: MAP_TARGET,
|
||||
layers: [
|
||||
customTileSource
|
||||
],
|
||||
view,
|
||||
});
|
||||
|
||||
export default map;
|
60
src/popup.js
Normal file
60
src/popup.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import map from "./map";
|
||||
import { Overlay } from "ol";
|
||||
import QRCode from "qrcode";
|
||||
import { el } from "./utils";
|
||||
import { toLonLat } from "ol/proj";
|
||||
|
||||
const popup = el("popup");
|
||||
const closer = el("popup-closer");
|
||||
|
||||
const popupName = el("popup-name");
|
||||
const popupAddress = el("popup-address");
|
||||
const popupLink = el("popup-link");
|
||||
const popupCanvas = el("popup-canvas");
|
||||
const popupSource = el("popup-source");
|
||||
const popupOperationTime = el("popup-operation-time");
|
||||
|
||||
const overlay = new Overlay({
|
||||
element: popup,
|
||||
autoPan: true,
|
||||
autoPanAnimation: {
|
||||
duration: 250,
|
||||
},
|
||||
});
|
||||
|
||||
map.addOverlay(overlay);
|
||||
|
||||
function close() {
|
||||
overlay.setPosition(undefined);
|
||||
closer.blur();
|
||||
return false;
|
||||
}
|
||||
|
||||
function onClick(event) {
|
||||
const feature = map.forEachFeatureAtPixel(event.pixel, function (feature) {
|
||||
return feature;
|
||||
});
|
||||
|
||||
if (!feature) return close();
|
||||
|
||||
const coordinates = feature.getGeometry().getCoordinates();
|
||||
|
||||
const [lon, lat] = toLonLat(coordinates);
|
||||
|
||||
popupName.textContent = feature.get("name");
|
||||
popupAddress.textContent = feature.get("address");
|
||||
popupAddress.href = `https://yandex.ru/maps/?whatshere[point]=${lon},${lat}&whatshere[zoom]=18&l=map`
|
||||
popupLink.href = feature.get("link");
|
||||
popupSource.href = feature.get("source");
|
||||
popupOperationTime.innerHTML = feature.get("operationTime") ?? "неизвестно";
|
||||
|
||||
QRCode.toCanvas(popupCanvas, feature.get("link"), function (error) {
|
||||
if (error) console.error(error);
|
||||
console.log("success!");
|
||||
});
|
||||
|
||||
overlay.setPosition(coordinates);
|
||||
}
|
||||
|
||||
closer.addEventListener("click", close);
|
||||
map.on("singleclick", onClick);
|
73
src/style.css
Normal file
73
src/style.css
Normal file
@@ -0,0 +1,73 @@
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
#map {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.ol-popup {
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid #cccccc;
|
||||
bottom: 12px;
|
||||
left: -50px;
|
||||
width: 300px; /* Увеличенная ширина */
|
||||
max-width: 300px; /* Максимальная ширина */
|
||||
font-size: 14px; /* Размер шрифта */
|
||||
color: #333; /* Цвет текста */
|
||||
line-height: 1.5; /* Межстрочный интервал */
|
||||
}
|
||||
|
||||
.ol-popup:after,
|
||||
.ol-popup:before {
|
||||
top: 100%;
|
||||
border: solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ol-popup:after {
|
||||
border-top-color: white;
|
||||
border-width: 10px;
|
||||
left: 48px;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.ol-popup:before {
|
||||
border-top-color: #cccccc;
|
||||
border-width: 11px;
|
||||
left: 48px;
|
||||
margin-left: -11px;
|
||||
}
|
||||
|
||||
#popup-closer {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 8px;
|
||||
font-size: 16px;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#popup-operation-time {
|
||||
white-space: pre-line
|
||||
}
|
52
src/utils.js
Normal file
52
src/utils.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { toLonLat } from "ol/proj";
|
||||
import { getDistance } from "ol/sphere";
|
||||
|
||||
|
||||
export function el(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
export function removeDuplicatesByRadius(features, radius) {
|
||||
const uniqueFeatures = [];
|
||||
const coordinatesSet = new Set();
|
||||
|
||||
features.forEach((feature) => {
|
||||
const coordinates = feature.getGeometry().getCoordinates();
|
||||
const lonLat = toLonLat(coordinates);
|
||||
let isDuplicate = false;
|
||||
|
||||
coordinatesSet.forEach((setCoordinates) => {
|
||||
if (getDistance(setCoordinates, lonLat) <= radius) {
|
||||
isDuplicate = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!isDuplicate) {
|
||||
uniqueFeatures.push(feature);
|
||||
coordinatesSet.add(lonLat);
|
||||
}
|
||||
});
|
||||
|
||||
return uniqueFeatures;
|
||||
}
|
||||
|
||||
export function removeDuplicatesByUrl(features) {
|
||||
const uniqueFeatures = [];
|
||||
const urlSet = new Set();
|
||||
|
||||
features.forEach((feature) => {
|
||||
let isDuplicate = false;
|
||||
const link = feature.get("link");
|
||||
|
||||
if (urlSet.has(link)) {
|
||||
isDuplicate = true;
|
||||
}
|
||||
|
||||
if (!isDuplicate) {
|
||||
uniqueFeatures.push(feature);
|
||||
urlSet.add(link);
|
||||
}
|
||||
});
|
||||
|
||||
return uniqueFeatures;
|
||||
}
|
Reference in New Issue
Block a user