Adding the Overpass API data to the map with the help of polygons
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
import { Header } from './header/header';
|
import { Header } from './header/header';
|
||||||
import { Map } from './map/map';
|
import { OSMMap } from './map/map';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
imports: [RouterOutlet, Header, Map],
|
imports: [RouterOutlet, Header, OSMMap],
|
||||||
templateUrl: './app.html',
|
templateUrl: './app.html',
|
||||||
styleUrl: './app.scss',
|
styleUrl: './app.scss',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import * as G from 'geojson';
|
|||||||
export const buildingSytle = {
|
export const buildingSytle = {
|
||||||
color: '#d62305', // Red border
|
color: '#d62305', // Red border
|
||||||
weight: 2, // Border width
|
weight: 2, // Border width
|
||||||
fillColor: '#d62305', // Red fill
|
fillColor: '#d4caca', // Red fill
|
||||||
fillOpacity: 0.1, // Semi-transparent fill
|
fillOpacity: 1, // Semi-transparent fill
|
||||||
};
|
};
|
||||||
|
|
||||||
// Example GeoJSON data
|
// Example GeoJSON data
|
||||||
@@ -28,9 +28,8 @@ export const buildings: G.FeatureCollection = {
|
|||||||
{
|
{
|
||||||
type: 'Feature',
|
type: 'Feature',
|
||||||
properties: {
|
properties: {
|
||||||
name: 'Gebäude E',
|
name: 'E',
|
||||||
building: 'university',
|
building: 'university',
|
||||||
center: [8.39007, 49.015],
|
|
||||||
},
|
},
|
||||||
geometry: {
|
geometry: {
|
||||||
type: 'Polygon',
|
type: 'Polygon',
|
||||||
@@ -48,9 +47,8 @@ export const buildings: G.FeatureCollection = {
|
|||||||
{
|
{
|
||||||
type: 'Feature',
|
type: 'Feature',
|
||||||
properties: {
|
properties: {
|
||||||
name: 'Gebäude F',
|
name: 'F',
|
||||||
building: 'university',
|
building: 'university',
|
||||||
center: [8.3901208597194, 49.0156137375335],
|
|
||||||
},
|
},
|
||||||
geometry: {
|
geometry: {
|
||||||
type: 'Polygon',
|
type: 'Polygon',
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
<div class="map-wrapper">
|
<div class="map-wrapper">
|
||||||
<div
|
<div
|
||||||
class="map-container"
|
class="map-container"
|
||||||
[ngClass]="isFullscreen ? 'map-fullscreen' : 'map-details'"
|
|
||||||
leaflet
|
leaflet
|
||||||
[leafletOptions]="options"
|
[leafletOptions]="options"
|
||||||
(leafletMapReady)="onMapReady($event)"
|
(leafletMapReady)="onMapReady($event)"
|
||||||
></div>
|
></div>
|
||||||
@if (!isFullscreen && details) {
|
|
||||||
<div class="details">
|
|
||||||
<h2>{{ details.name }}</h2>
|
|
||||||
<p>{{ details.building }}</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 54px;
|
top: 54px;
|
||||||
height: calc(100vh - 54px);
|
height: calc(100vh - 54px);
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-fullscreen {
|
.map-fullscreen {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Component, signal } from '@angular/core';
|
import { Component, signal } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { LeafletModule } from '@bluehalo/ngx-leaflet';
|
import { LeafletModule } from '@bluehalo/ngx-leaflet';
|
||||||
|
import { center } from '@turf/turf';
|
||||||
import { Overpass } from '../overpass';
|
import { Overpass } from '../overpass';
|
||||||
import * as L from 'leaflet';
|
import * as L from 'leaflet';
|
||||||
|
|
||||||
@@ -13,12 +14,11 @@ import * as E from './geojson';
|
|||||||
templateUrl: './map.html',
|
templateUrl: './map.html',
|
||||||
styleUrl: './map.scss',
|
styleUrl: './map.scss',
|
||||||
})
|
})
|
||||||
export class Map {
|
export class OSMMap {
|
||||||
private centerLocation = { lat: 49.015514096207895, lng: 8.391567600294243 };
|
private buildingLayer?: L.LayerGroup;
|
||||||
|
|
||||||
protected readonly title = signal('HKA-OSM');
|
protected readonly title = signal('HKA-OSM');
|
||||||
map?: L.Map;
|
map?: L.Map;
|
||||||
details?: D.Details;
|
|
||||||
|
|
||||||
isFullscreen = true;
|
isFullscreen = true;
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ export class Map {
|
|||||||
options: L.MapOptions = {
|
options: L.MapOptions = {
|
||||||
layers: [this.osm],
|
layers: [this.osm],
|
||||||
zoom: 18, // Zoom level
|
zoom: 18, // Zoom level
|
||||||
center: L.latLng(this.centerLocation.lat, this.centerLocation.lng),
|
center: this.getCenter(), // Center of the map
|
||||||
maxBounds: L.latLngBounds(
|
maxBounds: L.latLngBounds(
|
||||||
L.latLng(49.014442, 8.387954), // Southwest corner
|
L.latLng(49.014442, 8.387954), // Southwest corner
|
||||||
L.latLng(49.017847, 8.395448), // Northeast corner
|
L.latLng(49.017847, 8.395448), // Northeast corner
|
||||||
@@ -51,7 +51,6 @@ export class Map {
|
|||||||
this.map = map;
|
this.map = map;
|
||||||
this.layerControl.addTo(this.map);
|
this.layerControl.addTo(this.map);
|
||||||
this.loadBuildings().addTo(this.map);
|
this.loadBuildings().addTo(this.map);
|
||||||
this.queryBuildings();
|
|
||||||
console.log('Map is ready');
|
console.log('Map is ready');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,23 +62,14 @@ export class Map {
|
|||||||
click: (e) => {
|
click: (e) => {
|
||||||
this.isFullscreen = !this.isFullscreen;
|
this.isFullscreen = !this.isFullscreen;
|
||||||
if (!this.isFullscreen) {
|
if (!this.isFullscreen) {
|
||||||
this.map?.setView(
|
this.map?.setView(this.getCenter(feature.geometry), 20);
|
||||||
L.latLng(feature.properties.center[1], feature.properties.center[0]),
|
this.buildingLayer = this.queryBuildings(feature.properties.name);
|
||||||
20,
|
this.buildingLayer.addTo(this.map!);
|
||||||
);
|
|
||||||
//FIXME: Disable dragging and keyboard interactions when in fullscreen mode
|
|
||||||
this.map?.dragging.disable() &&
|
|
||||||
this.map?.keyboard.disable() &&
|
|
||||||
this.map?.scrollWheelZoom.disable();
|
|
||||||
this.details = new D.Details(feature.properties.name, feature.properties.building);
|
|
||||||
} else {
|
} else {
|
||||||
this.map?.setView(L.latLng(this.centerLocation.lat, this.centerLocation.lng), 18);
|
this.map?.setView(this.getCenter(), 18);
|
||||||
|
if (this.buildingLayer) {
|
||||||
//FIXME: Enable dragging and keyboard interactions when in fullscreen mode
|
this.map?.removeLayer(this.buildingLayer);
|
||||||
this.map?.dragging.enable() &&
|
}
|
||||||
this.map?.keyboard.enable() &&
|
|
||||||
this.map?.scrollWheelZoom.enable();
|
|
||||||
this.details = undefined;
|
|
||||||
}
|
}
|
||||||
console.log('Set isFullscreen to:', this.isFullscreen);
|
console.log('Set isFullscreen to:', this.isFullscreen);
|
||||||
console.log('Clicked on feature:', feature.properties?.name);
|
console.log('Clicked on feature:', feature.properties?.name);
|
||||||
@@ -95,10 +85,49 @@ export class Map {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
queryBuildings() {
|
queryBuildings(building_name: string) {
|
||||||
this.overpass.fetchBuildings('E').subscribe((data) => {
|
// Get the building name from the feature properties and query the Overpass API for detailed building data,
|
||||||
|
// including indoor features.
|
||||||
|
|
||||||
|
// Create a new LayerGroup to hold the building polygons
|
||||||
|
const resultingLayer = new L.LayerGroup();
|
||||||
|
this.overpass.fetchBuildings(building_name).subscribe((data) => {
|
||||||
console.log('Fetched building data from Overpass API:', data);
|
console.log('Fetched building data from Overpass API:', data);
|
||||||
// Here you would typically process the data and add it to the map
|
|
||||||
|
// Create a mapping of node IDs to their coordinates
|
||||||
|
const nodes: Map<number, L.LatLng> = new Map();
|
||||||
|
data.elements.forEach((element: any) => {
|
||||||
|
if (element.type === 'node') {
|
||||||
|
nodes.set(element.id, new L.LatLng(element.lat, element.lon));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process ways and create polygons
|
||||||
|
// TODO: Change to features, to also add the meta data of the building, e.g. the name of the room
|
||||||
|
data.elements.forEach((element: any) => {
|
||||||
|
if (element.type === 'way') {
|
||||||
|
const coordinates: L.LatLng[] = [];
|
||||||
|
element.nodes.forEach((nodeId: number) => {
|
||||||
|
coordinates.push(nodes.get(nodeId)!);
|
||||||
|
});
|
||||||
|
L.polygon(coordinates, { color: '#999999', fillColor: '#e0e0e0', fillOpacity: 1 }).addTo(
|
||||||
|
resultingLayer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
return resultingLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCenter(feature?: any): L.LatLng {
|
||||||
|
// Calculate the center of the feature using Turf.js, or use a default center if no feature is provided.
|
||||||
|
let centerPoint: number[] = [];
|
||||||
|
if (feature) {
|
||||||
|
centerPoint = center(feature).geometry.coordinates.reverse();
|
||||||
|
} else {
|
||||||
|
centerPoint = [49.015514096207895, 8.391567600294243];
|
||||||
|
}
|
||||||
|
const result = new L.LatLng(centerPoint[0], centerPoint[1]);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,25 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
|
export interface OverpassElementNode {
|
||||||
|
type: 'node';
|
||||||
|
id: number;
|
||||||
|
lat: number;
|
||||||
|
lon: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OverpassElementWay {
|
||||||
|
type: 'way';
|
||||||
|
id: number;
|
||||||
|
geometry?: Array<{ lat: number; lon: number }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OverpassElement = OverpassElementNode | OverpassElementWay;
|
||||||
|
|
||||||
|
export interface OverpassResponse {
|
||||||
|
elements: OverpassElement[];
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
@@ -13,7 +32,7 @@ export class Overpass {
|
|||||||
const header = { 'Content-Type': 'text/plain' };
|
const header = { 'Content-Type': 'text/plain' };
|
||||||
const query = `
|
const query = `
|
||||||
[out:json][timeout:25];
|
[out:json][timeout:25];
|
||||||
nwr["name"="${building_name}"](49.014442, 8.387954, 49.017847, 8.395448)->.building;
|
nwr["building"]["operator"="Hochschule Karlsruhe"]["name"="${building_name}"](49.014442, 8.387954, 49.017847, 8.395448)->.building;
|
||||||
(
|
(
|
||||||
nwr["indoor"="room"](area.building);
|
nwr["indoor"="room"](area.building);
|
||||||
);
|
);
|
||||||
@@ -21,6 +40,6 @@ export class Overpass {
|
|||||||
out body;
|
out body;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return this.http.post(this.apiUrl, query, { headers: header });
|
return this.http.post<OverpassResponse>(this.apiUrl, query, { headers: header });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user