225 lines
6.6 KiB
JavaScript
225 lines
6.6 KiB
JavaScript
import "./style.css";
|
||
|
||
import { Map, View, Overlay, Feature } from "ol";
|
||
import { Icon, Style } from "ol/style";
|
||
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer";
|
||
import { TileJSON, Vector as VectorSource } from "ol/source";
|
||
import { useGeographic } from "ol/proj.js";
|
||
import { Control, defaults as defaultControls } from "ol/control.js";
|
||
import Point from "ol/geom/Point.js";
|
||
import { Popover } from "bootstrap";
|
||
|
||
// specify to use real lon/lat coordinates
|
||
useGeographic();
|
||
|
||
// state bool for locking pin overlay while adding pin
|
||
let pinning = false;
|
||
|
||
// creates pins
|
||
const makePin = (lon, lat, storeName, cheapest, flozPrice) => {
|
||
// define pin graphics (since this is all inline)
|
||
const pinSVG = `<svg width="32px" height="32px" viewBox="-0.12 -0.12 12.24 12.24" enable-background="new 0 0 12 12" id="Слой_1" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#000000" stroke="#000000" stroke-width="0.12000000000000002"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M6,0C3.2385864,0,1,2.2385864,1,5s2.5,5,5,7c2.5-2,5-4.2385864,5-7S8.7614136,0,6,0z M6,7 C4.8954468,7,4,6.1045532,4,5s0.8954468-2,2-2s2,0.8954468,2,2S7.1045532,7,6,7z" fill="#ed333b"></path></g></svg>`;
|
||
const pinSVGBlob = new Blob([pinSVG], {
|
||
type: "image/svg+xml",
|
||
});
|
||
const pinImageURL = URL.createObjectURL(pinSVGBlob);
|
||
|
||
// define style for all pins
|
||
const pinStyle = new Style({
|
||
image: new Icon({
|
||
src: pinImageURL,
|
||
anchor: [0.5, 1],
|
||
}),
|
||
});
|
||
|
||
// create pin as feature
|
||
const pin = new Feature({
|
||
geometry: new Point([lon, lat]),
|
||
store: storeName,
|
||
cheapestItem: cheapest,
|
||
pricePerOz: flozPrice,
|
||
});
|
||
|
||
// set style for pin
|
||
pin.setStyle(pinStyle);
|
||
|
||
return pin;
|
||
};
|
||
|
||
// create new VectorSource for pins
|
||
const pinSource = new VectorSource();
|
||
|
||
// FLASK PULL LOOP
|
||
|
||
// test
|
||
const pin = makePin(0, 0, "Null Store", "Modelo", "0.30");
|
||
|
||
// add pin
|
||
pinSource.addFeature(pin);
|
||
|
||
// END FLASK PULL LOOP
|
||
|
||
// create vector layer for rendering pins and popups
|
||
const pinLayer = new VectorLayer({
|
||
source: pinSource,
|
||
});
|
||
|
||
// create button
|
||
// made in global scope so it can be modified
|
||
const button = document.createElement("button");
|
||
button.textContent = "New";
|
||
button.addEventListener("click", newButtonHandler, false);
|
||
function newButtonHandler() {
|
||
window.ReactNativeWebView?.postMessage("new pin start");
|
||
}
|
||
|
||
// add button to control
|
||
class ButtonControl extends Control {
|
||
constructor(opt_options) {
|
||
const options = opt_options || {};
|
||
|
||
const element = document.createElement("div");
|
||
element.className = "new-button ol-unselectable ol-control";
|
||
element.appendChild(button);
|
||
|
||
super({
|
||
element: element,
|
||
target: options.target,
|
||
});
|
||
}
|
||
}
|
||
|
||
// create OSM map
|
||
const map = new Map({
|
||
controls: defaultControls().extend([new ButtonControl()]),
|
||
target: document.getElementById("map"),
|
||
layers: [
|
||
new TileLayer({
|
||
source: new TileJSON({
|
||
url: "https://api.maptiler.com/maps/hybrid/tiles.json?key=iE1hOruK6f8SDwxrEIir",
|
||
tileSize: 512,
|
||
crossOrigin: "anonymous",
|
||
}),
|
||
}),
|
||
],
|
||
// initially view at Null Point, 2x zoom
|
||
view: new View({
|
||
center: [0, 0],
|
||
zoom: 2,
|
||
maxZoom: 20,
|
||
}),
|
||
});
|
||
|
||
// popup element to display when pin clicked
|
||
const element = document.getElementById("popup");
|
||
const popup = new Overlay({
|
||
element: element,
|
||
positioning: "bottom-center",
|
||
stopEvent: false,
|
||
});
|
||
|
||
// add popup overlay to map
|
||
map.addOverlay(popup);
|
||
|
||
// set state for popup visibility
|
||
let popover;
|
||
const disposePopover = () => {
|
||
if (popover) {
|
||
popover.dispose();
|
||
popover = undefined;
|
||
}
|
||
};
|
||
|
||
// on click events
|
||
map.on("click", (event) => {
|
||
console.log(event.coordinate);
|
||
// get features at pixel
|
||
const feature = map.forEachFeatureAtPixel(event.pixel, (feature) => feature);
|
||
// preemptively remove any previous popup overlays
|
||
disposePopover();
|
||
// if no pins at location, do nothing
|
||
if (!feature) return;
|
||
// set popup window position
|
||
popup.setPosition(event.coordinate);
|
||
// create popover at position
|
||
popover = new Popover(element, {
|
||
placement: "top",
|
||
html: true,
|
||
title: feature.get("store"),
|
||
content: `Cheapest Item: ${feature.get(
|
||
"cheapestItem"
|
||
)}<br/>$ / oz.: $0.30<br/><a href=''>More...</a><br/>`,
|
||
});
|
||
// show popover
|
||
popover.show();
|
||
});
|
||
|
||
// set pointer to cursor when hovering over pin
|
||
map.on("pointermove", (event) => {
|
||
// if there is a pin at the pixel the user is hovering over
|
||
if (map.hasFeatureAtPixel(map.getEventPixel(event.originalEvent))) {
|
||
// set the cursor to pointer cursor
|
||
map.getTarget().style.cursor = "pointer";
|
||
}
|
||
// otherwise make cursor normal again
|
||
else map.getTarget().style.cursor = "";
|
||
});
|
||
|
||
// close popup when map moved
|
||
map.on("movestart", disposePopover);
|
||
|
||
// sets listener to listen for zoom changes
|
||
map.getView().on("change:resolution", () => {
|
||
map.getView().getZoom() < 5
|
||
? map.getLayers().removeAt(1)
|
||
: (() => {
|
||
if (map.getLayers().getLength() === 1) {
|
||
if (!pinning) map.getLayers().extend([pinLayer]);
|
||
}
|
||
})();
|
||
});
|
||
|
||
// centers location on user based on location information passed in from expo-location
|
||
window.passLocation = (lon, lat) => {
|
||
const view = new View({
|
||
center: [lon, lat],
|
||
zoom: 18,
|
||
maxZoom: 20,
|
||
});
|
||
|
||
map.setView(view);
|
||
|
||
// sets NEW listener to listen for zoom changes
|
||
view.on("change:resolution", () => {
|
||
view.getZoom() < 5
|
||
? map.getLayers().removeAt(1)
|
||
: (() => {
|
||
if (map.getLayers().getLength() === 1) {
|
||
if (!pinning) map.getLayers().extend([pinLayer]);
|
||
}
|
||
})();
|
||
});
|
||
};
|
||
|
||
// place pin interaction
|
||
window.placePin = () => {
|
||
// removes listener on button in case someone wants to mash it
|
||
button.removeEventListener("click", newButtonHandler, false);
|
||
// removes pin layer
|
||
pinning = true;
|
||
map.getLayers().removeAt(1);
|
||
// adds pin in center of view
|
||
document.getElementById("centerPin").style.display = "block";
|
||
// changes new button to OK button
|
||
button.textContent = "OK";
|
||
// OK button has event listener that on click
|
||
function OKButtonHandler() {
|
||
// removes listener on button in case someone wants to mash it
|
||
button.removeEventListener("click", OKButtonHandler, false);
|
||
// removes pin in center of view
|
||
document.getElementById("centerPin").style.display = "none";
|
||
// emits to RN
|
||
window.ReactNativeWebView?.postMessage(`@${map.getView().getCenter()}`);
|
||
}
|
||
button.addEventListener("click", OKButtonHandler, false);
|
||
};
|