burguillos.info/js-src/conquer/index.ts

402 lines
14 KiB
TypeScript

import Map from "ol/Map"
import MapEvent from "ol/MapEvent"
import MapBrowserEvent from "ol/MapBrowserEvent"
import View from "ol/View"
import Projection from "ol/proj/Projection.js"
import TileLayer from 'ol/layer/Tile'
import OSM from 'ol/source/OSM'
import * as olProj from "ol/proj"
import Feature from 'ol/Feature'
import Point from 'ol/geom/Point'
import VectorLayer from 'ol/layer/Vector'
import VectorSource from 'ol/source/Vector'
import Stroke from 'ol/style/Stroke'
import Fill from 'ol/style/Fill'
import CircleStyle from 'ol/style/Circle'
import Style from 'ol/style/Style'
import ConquerLogin from '@burguillosinfo/conquer/login'
import InterfaceManager from '@burguillosinfo/conquer/interface-manager'
import SelfPlayerUI from '@burguillosinfo/conquer/interface/self-player'
import CreateNode from '@burguillosinfo/conquer/create-node'
import MapState from '@burguillosinfo/conquer/map-state'
import MapNode from '@burguillosinfo/conquer/map-node'
type StylesInterface = Record<string, Style>
export default class Conquer {
private conquerContainer: HTMLDivElement
private map: Map
private currentLongitude: number
private currentLatitude: number
private rotationOffset = 0
private heading = 0
private disableSetRotationOffset = false
private currentPositionFeature: Feature | null
private vectorLayer: VectorLayer<VectorSource> | null = null
private alpha = 0
private beta = 0
private gamma = 0
private conquerLogin: ConquerLogin
private selfPlayerUI: SelfPlayerUI | null = null
private interfaceManager: InterfaceManager
private firstSetCenter = true
private firstSetRotation = true
private state: MapState = MapState.NOTHING
private createNodeObject: CreateNode
private serverNodes: Record<string, MapNode> = {}
public getServerNodes(): Record<string, MapNode> {
return this.serverNodes
}
public getState(): MapState {
return this.state
}
public setState(state: MapState) {
this.state = state
this.refreshState()
}
public removeState(state: MapState) {
this.state &= ~state
this.refreshState()
}
public addState(state: MapState) {
this.state |= state
this.refreshState()
}
private refreshState(): void {
this.createNodeObject.refreshState()
return
}
static start() {
const conquerContainer = document.querySelector(".conquer-container")
if (conquerContainer === null || !(conquerContainer instanceof HTMLDivElement)) {
Conquer.fail('.conquer-container is not a div.')
}
const conquer = new Conquer(conquerContainer)
conquer.run()
}
setCenterDisplaced(lat: number, lon: number) {
if (this.firstSetCenter || !(this.state & MapState.FREE_MOVE)) {
const olCoordinates = this.realCoordinatesToOl(lat, lon)
const size = this.map.getSize()
if (size === undefined) {
return
}
this.map.getView().centerOn(olCoordinates, size, [size[0]/2, size[1]-60])
this.firstSetCenter = false
}
}
static fail(error: string): never {
alert('Error de interfaz')
throw new Error(error)
}
public isStateCreatingNode(): boolean {
return !!(this.getState() & MapState.CREATE_NODE)
}
public isStateSelectWhereToCreateNode(): boolean {
return !!(this.getState() & MapState.SELECT_WHERE_TO_CREATE_NODE)
}
private createNodeCounter = 0
async onClickWhereToCreateNode(event: MapEvent) {
if (!(event instanceof MapBrowserEvent)) {
return
}
const pixel = event.pixel
const coordinates = this.map.getCoordinateFromPixel(pixel)
const feature = new Feature({
geometry: new Point(coordinates)
})
console.log(coordinates)
const style = new Style({
image: new CircleStyle({
radius: 14,
fill: new Fill({color: 'white'}),
stroke: new Stroke({
color: 'gray',
width: 2,
})
})
})
const mapNode = new MapNode(style, feature, `server-node-${++this.createNodeCounter}`)
this.getServerNodes()[mapNode.getId()] = mapNode
this.removeState(MapState.SELECT_WHERE_TO_CREATE_NODE)
this.refreshLayers()
}
async onClickMap(event: MapEvent): Promise<void> {
if (this.isStateCreatingNode() && this.isStateSelectWhereToCreateNode()) {
this.onClickWhereToCreateNode(event)
}
if (!(this.getState() & MapState.NORMAL)) {
return
}
if (this.vectorLayer === null) {
return
}
if (!(event instanceof MapBrowserEvent)) {
return
}
if (event.dragging) {
return
}
const pixel = event.pixel
const features = this.map.getFeaturesAtPixel(pixel)
const feature = features.length ? features[0] : undefined
if (feature === undefined) {
return
}
if (!(feature instanceof Feature)) {
return
}
this.onClickFeature(feature)
}
async onClickSelf(): Promise<void> {
if (!(this.state & MapState.NORMAL)) {
return
}
const selfPlayerUI = new SelfPlayerUI()
selfPlayerUI.on('close', () => {
this.interfaceManager.remove(selfPlayerUI)
})
selfPlayerUI.on('createNodeStart', () => {
this.addState(MapState.CREATE_NODE)
this.removeState(MapState.NORMAL)
})
this.interfaceManager.push(selfPlayerUI)
this.selfPlayerUI = selfPlayerUI
}
private isFeatureEnabledMap: Record<string, boolean> = {}
async onClickFeature(feature: Feature): Promise<void> {
if (this.isFeatureEnabledMap[feature.getProperties().type] === undefined) {
this.isFeatureEnabledMap[feature.getProperties().type] = true
}
if (!this.isFeatureEnabledMap[feature.getProperties().type]) {
return
}
this.isFeatureEnabledMap[feature.getProperties().type] = false
window.setTimeout(() => {
this.isFeatureEnabledMap[feature.getProperties().type] = true
}, 100);
if (feature === this.currentPositionFeature) {
this.onClickSelf()
return
}
}
async onLoginSuccess(): Promise<void> {
const currentPositionFeature = this.currentPositionFeature
if (currentPositionFeature === null) {
return
}
this.map.on('click', (event: MapEvent) => {
this.onClickMap(event)
})
}
private runPreStartState(): void {
const createNodeObject = new CreateNode(this)
this.createNodeObject = createNodeObject
const interfaceManager = new InterfaceManager()
this.interfaceManager = interfaceManager
const conquerLogin = new ConquerLogin(interfaceManager)
conquerLogin.on('login', () => {
this.onLoginSuccess()
})
conquerLogin.start()
this.conquerLogin = conquerLogin
}
async run() {
this.runPreStartState()
this.setState(MapState.NORMAL)
const conquerContainer = this.conquerContainer
//layer.on('prerender', (evt) => {
// // return
// if (evt.context) {
// const context = evt.context as CanvasRenderingContext2D
// context.filter = 'grayscale(80%) invert(100%) '
// context.globalCompositeOperation = 'source-over'
// }
//})
//layer.on('postrender', (evt) => {
// if (evt.context) {
// const context = evt.context as CanvasRenderingContext2D
// context.filter = 'none'
// }
//})
olProj.useGeographic()
const osm = new OSM()
osm.setUrls([`${window.location.protocol}//${window.location.hostname}:${window.location.port}/conquer/tile/{z}/{x}/{y}.png`])
this.map = new Map({
target: conquerContainer,
layers: [
new TileLayer({
source: osm
})
],
view: new View({
zoom: 19,
maxZoom: 22,
}),
})
this.setLocationChangeTriggers()
this.setRotationChangeTriggers()
}
setRotationChangeTriggers(): void {
if (window.DeviceOrientationEvent) {
window.addEventListener("deviceorientation", (event) => {
if (event.alpha !== null && event.beta !== null && event.gamma !== null) {
this.onRotate(event.alpha, event.beta, event.gamma)
}
}, true)
}
}
addCurrentLocationMarkerToMap(currentLatitude: number,
currentLongitude: number) {
const currentPositionFeature = new Feature({
type: 'currentPositionFeature',
geometry: new Point(this.realCoordinatesToOl(currentLatitude, currentLongitude))
})
this.currentPositionFeature = currentPositionFeature
}
processLocation(location: GeolocationPosition) {
this.currentLatitude = location.coords.latitude
this.currentLongitude = location. coords.longitude
if (location.coords.heading !== null && (this.alpha != 0 || this.beta != 0 || this.gamma != 0) && !this.disableSetRotationOffset) {
this.disableSetRotationOffset = true
this.heading = location.coords.heading
this.rotationOffset = this.compassHeading(this.alpha, this.beta, this.gamma) + (location.coords.heading*Math.PI*2)/360
}
this.setCenterDisplaced(this.currentLatitude, this.currentLongitude)
this.addCurrentLocationMarkerToMap(this.currentLatitude, this.currentLongitude)
this.refreshLayers()
}
private refreshLayers(): void {
if (this.currentPositionFeature === null) {
return
}
const styles: StylesInterface = {
currentPositionFeature: new Style({
image: new CircleStyle({
radius: 14,
fill: new Fill({color: 'white'}),
stroke: new Stroke({
color: 'blue',
width: 2,
})
})
})
};
const features = [this.currentPositionFeature]
for (const key in this.getServerNodes()) {
styles[key] = this.getServerNodes()[key].getStyle()
features.push(this.getServerNodes()[key].getNode())
}
const vectorLayer = new VectorLayer<VectorSource>({
source: new VectorSource({
features: features
}),
})
if (this.vectorLayer !== null) {
this.map.removeLayer(this.vectorLayer)
}
vectorLayer.setStyle((feature) => {
return styles[feature.getProperties().type]
})
this.map.addLayer(vectorLayer)
this.vectorLayer = vectorLayer
this.map.on('click', (event: MapEvent) => {
this.onClickMap(event)
})
}
setLocationChangeTriggers(): void {
window.setInterval(() => {
this.disableSetRotationOffset = false
}, 10000)
this.currentPositionFeature = null
window.setTimeout(() => {
window.setInterval(() => {
navigator.geolocation.getCurrentPosition((location) => {
this.processLocation(location)
}, () => {
return
}, {
enableHighAccuracy: true,
})
}, 3000)
}, 1000)
// const initialLatitude = 37.58237
//const initialLongitude = -5.96766
const initialLongitude = 2.500845037550267
const initialLatitude = 48.81050698635832
this.setCenterDisplaced(initialLatitude, initialLongitude)
this.addCurrentLocationMarkerToMap(initialLatitude, initialLongitude)
this.refreshLayers()
navigator.geolocation.watchPosition((location) => {
this.processLocation(location)
}, (err) => {
return
}, {
enableHighAccuracy: true,
})
}
realCoordinatesToOl(lat: number, lon: number): number[] {
return olProj.transform(
[lon, lat],
new Projection({ code: "WGS84" }),
new Projection({ code: "EPSG:900913" }),
)
}
compassHeading(alpha:number, beta:number, gamma:number): number {
const alphaRad = alpha * (Math.PI / 180)
return alphaRad
}
logToServer(logValue: string) {
const urlLog = new URL('/conquer/log', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
urlLog.searchParams.append('log', logValue)
fetch(urlLog).then(() => {
return
}).catch((error) => {
console.error(error)
})
}
enabledOnRotate = true
onRotate(alpha: number, beta: number, gamma: number) {
if (this.enabledOnRotate) {
this.alpha = alpha
this.beta = beta
this.gamma = gamma
this.enabledOnRotate = false
if (this.firstSetRotation || !(this.state & MapState.FREE_ROTATION)) {
this.map.getView().setRotation((this.compassHeading(alpha, beta, gamma) - this.rotationOffset))
this.firstSetRotation = false
}
window.setTimeout(() => {
this.enabledOnRotate = true
}, 10)
}
this.setCenterDisplaced(this.currentLatitude, this.currentLongitude)
}
constructor(conquerContainer: HTMLDivElement) {
this.conquerContainer = conquerContainer
}
}