beerbuddy/openlayers/main.js
ak 9a9e6ac209 progress update 2023-11-29
backend code complete, to be uploaded to deta
2023-11-29 18:17:43 -08:00

225 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
};