add a bunch of (false) information to the map
This commit is contained in:
387
ui/src/App.vue
387
ui/src/App.vue
@@ -20,10 +20,24 @@
|
||||
</div>
|
||||
|
||||
<div class="control-section">
|
||||
<label>
|
||||
<input type="checkbox" v-model="showOctagon" @change="toggleOctagon">
|
||||
Show Octagon
|
||||
<label class="section-header" @click="historicMarkersExpanded = !historicMarkersExpanded" style="cursor: pointer;">
|
||||
{{ historicMarkersExpanded ? '▼' : '▶' }} Historic Markers
|
||||
</label>
|
||||
<div v-if="historicMarkersExpanded" class="subsection">
|
||||
<button class="hide-all-btn" @click="hideAllHistoricMarkers">
|
||||
{{ allHistoricMarkersHidden ? 'Show All' : 'Hide All' }}
|
||||
</button>
|
||||
<div v-for="site in KNOWN_SITES" :key="site.name" class="site-control">
|
||||
<label>
|
||||
<input type="checkbox" v-model="visibleSites[site.name]" @change="toggleSite(site.name)">
|
||||
{{ site.name }}
|
||||
</label>
|
||||
<button class="jump-btn" @click="jumpToSite(site)" title="Jump to location">📍</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-section">
|
||||
<label>
|
||||
<input type="checkbox" v-model="showLidar" @change="toggleLidar">
|
||||
Show Lidar
|
||||
@@ -111,6 +125,11 @@
|
||||
<div>Bearing: {{ popup.feature.properties.bearing.toFixed(1) }}°</div>
|
||||
<button @click="deleteFeature(popup.feature.id)" class="popup-btn danger">Delete</button>
|
||||
</div>
|
||||
<div v-else-if="popup.type === 'site'" class="site-popup">
|
||||
<strong>{{ popup.feature.properties.name }}</strong>
|
||||
<div class="site-type">{{ popup.feature.properties.type === 'road_confirmed' ? 'Confirmed Road Segment' : 'Earthwork' }}</div>
|
||||
<div class="site-description">{{ popup.feature.properties.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="popup.visible = false" class="popup-close">×</button>
|
||||
</div>
|
||||
@@ -138,7 +157,9 @@ const sandboxRef = ref(null);
|
||||
const sandboxVisible = ref(false);
|
||||
const sandboxOffscreen = ref(false);
|
||||
const baseLayer = ref('osm');
|
||||
const showOctagon = ref(true);
|
||||
const historicMarkersExpanded = ref(true);
|
||||
const visibleSites = ref({});
|
||||
const allHistoricMarkersHidden = ref(false);
|
||||
const showLidar = ref(true);
|
||||
const showGeometry = ref(true);
|
||||
const imperialUnits = ref(false);
|
||||
@@ -155,7 +176,7 @@ const nextFeatureId = ref(1);
|
||||
|
||||
// UI state
|
||||
const contextMenu = ref({ visible: false, x: 0, y: 0, lngLat: null, hasTile: false });
|
||||
const popup = ref({ visible: false, x: 0, y: 0, type: null, feature: null });
|
||||
const popup = ref({ visible: false, x: 0, y: 0, type: null, feature: null, lngLat: null });
|
||||
|
||||
// Map instance
|
||||
let map = null;
|
||||
@@ -237,29 +258,16 @@ const KNOWN_SITES = [
|
||||
"tiles":["BS18430458"]}
|
||||
];
|
||||
|
||||
// Tile names to load
|
||||
const TILE_NAMES = [
|
||||
'BS19820747',
|
||||
'BS19820748',
|
||||
'BS19830747',
|
||||
'BS19830748',
|
||||
'BS19820746',
|
||||
'BS19810747',
|
||||
'BS19810746',
|
||||
];
|
||||
|
||||
// Newark Octagon coordinates
|
||||
const octagonCoords = [
|
||||
[-82.44123, 40.05443],
|
||||
[-82.44260, 40.05309],
|
||||
[-82.44464, 40.05237],
|
||||
[-82.44631, 40.05342],
|
||||
[-82.44728, 40.05500],
|
||||
[-82.44589, 40.05633],
|
||||
[-82.44389, 40.05698],
|
||||
[-82.44216, 40.05595],
|
||||
[-82.44123, 40.05443],
|
||||
];
|
||||
// Tile names to load - merged from all KNOWN_SITES
|
||||
const TILE_NAMES = (() => {
|
||||
const allTiles = new Set();
|
||||
KNOWN_SITES.forEach(site => {
|
||||
if (site.tiles) {
|
||||
site.tiles.forEach(tile => allTiles.add(tile));
|
||||
}
|
||||
});
|
||||
return Array.from(allTiles);
|
||||
})();
|
||||
|
||||
const octagonCenter = [-82.44383, 40.05469];
|
||||
|
||||
@@ -521,12 +529,64 @@ function updateBaseLayer() {
|
||||
}
|
||||
}
|
||||
|
||||
function toggleOctagon() {
|
||||
function toggleGeometry() {
|
||||
if (!map) return;
|
||||
|
||||
const visibility = showOctagon.value ? 'visible' : 'none';
|
||||
map.setLayoutProperty('octagon-fill', 'visibility', visibility);
|
||||
map.setLayoutProperty('octagon-outline', 'visibility', visibility);
|
||||
const visibility = showGeometry.value ? 'visible' : 'none';
|
||||
if (map.getLayer('geometry-pins')) {
|
||||
map.setLayoutProperty('geometry-pins', 'visibility', visibility);
|
||||
}
|
||||
if (map.getLayer('geometry-lines')) {
|
||||
map.setLayoutProperty('geometry-lines', 'visibility', visibility);
|
||||
}
|
||||
if (map.getLayer('geometry-labels')) {
|
||||
map.setLayoutProperty('geometry-labels', 'visibility', visibility);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSite(siteName) {
|
||||
if (!map) return;
|
||||
|
||||
const visible = visibleSites.value[siteName];
|
||||
const visibility = visible ? 'visible' : 'none';
|
||||
|
||||
// Toggle marker
|
||||
const markerLayerId = `site-marker-${siteName}`;
|
||||
if (map.getLayer(markerLayerId)) {
|
||||
map.setLayoutProperty(markerLayerId, 'visibility', visibility);
|
||||
}
|
||||
|
||||
// Toggle overlay geometry
|
||||
const overlayLayerId = `site-overlay-${siteName}`;
|
||||
if (map.getLayer(overlayLayerId)) {
|
||||
map.setLayoutProperty(overlayLayerId, 'visibility', visibility);
|
||||
}
|
||||
|
||||
// Toggle polyline
|
||||
const polylineLayerId = `site-polyline-${siteName}`;
|
||||
if (map.getLayer(polylineLayerId)) {
|
||||
map.setLayoutProperty(polylineLayerId, 'visibility', visibility);
|
||||
}
|
||||
}
|
||||
|
||||
function jumpToSite(site) {
|
||||
if (!map) return;
|
||||
|
||||
const coords = site.coordinates[0];
|
||||
map.flyTo({
|
||||
center: coords,
|
||||
zoom: 15,
|
||||
duration: 1500
|
||||
});
|
||||
}
|
||||
|
||||
function hideAllHistoricMarkers() {
|
||||
allHistoricMarkersHidden.value = !allHistoricMarkersHidden.value;
|
||||
|
||||
KNOWN_SITES.forEach(site => {
|
||||
visibleSites.value[site.name] = !allHistoricMarkersHidden.value;
|
||||
toggleSite(site.name);
|
||||
});
|
||||
}
|
||||
|
||||
function toggleLidar() {
|
||||
@@ -553,21 +613,6 @@ function updateLidarOpacity() {
|
||||
}
|
||||
}
|
||||
|
||||
function toggleGeometry() {
|
||||
if (!map) return;
|
||||
|
||||
const visibility = showGeometry.value ? 'visible' : 'none';
|
||||
if (map.getLayer('geometry-pins')) {
|
||||
map.setLayoutProperty('geometry-pins', 'visibility', visibility);
|
||||
}
|
||||
if (map.getLayer('geometry-lines')) {
|
||||
map.setLayoutProperty('geometry-lines', 'visibility', visibility);
|
||||
}
|
||||
if (map.getLayer('geometry-labels')) {
|
||||
map.setLayoutProperty('geometry-labels', 'visibility', visibility);
|
||||
}
|
||||
}
|
||||
|
||||
function openSandbox() {
|
||||
if (Object.keys(tileCache.value).length > 0) {
|
||||
sandboxVisible.value = true;
|
||||
@@ -826,12 +871,17 @@ onMounted(() => {
|
||||
const featureData = geometryFeatures.value.features.find(f => f.id === feature.properties.id);
|
||||
|
||||
if (featureData) {
|
||||
const coords = featureData.geometry.type === 'Point'
|
||||
? featureData.geometry.coordinates
|
||||
: featureData.geometry.coordinates[0]; // Use first point of line
|
||||
|
||||
popup.value = {
|
||||
visible: true,
|
||||
x: e.point.x,
|
||||
y: e.point.y,
|
||||
type: featureData.properties.type,
|
||||
feature: featureData
|
||||
feature: featureData,
|
||||
lngLat: coords
|
||||
};
|
||||
}
|
||||
} else {
|
||||
@@ -870,37 +920,144 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
map.on('load', async () => {
|
||||
// Add octagon overlay
|
||||
map.addSource('octagon', {
|
||||
type: 'geojson',
|
||||
data: {
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [octagonCoords]
|
||||
// Initialize visibility state for all sites
|
||||
KNOWN_SITES.forEach(site => {
|
||||
visibleSites.value[site.name] = true;
|
||||
});
|
||||
|
||||
// Add historic site markers and overlays
|
||||
KNOWN_SITES.forEach(site => {
|
||||
// Add marker
|
||||
const markerCoords = site.coordinates[0];
|
||||
map.addSource(`site-marker-${site.name}`, {
|
||||
type: 'geojson',
|
||||
data: {
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: markerCoords
|
||||
},
|
||||
properties: {
|
||||
name: site.name,
|
||||
description: site.description,
|
||||
type: site.type
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: `site-marker-${site.name}`,
|
||||
type: 'circle',
|
||||
source: `site-marker-${site.name}`,
|
||||
paint: {
|
||||
'circle-radius': 10,
|
||||
'circle-color': site.type === 'road_confirmed' ? '#4A9EFF' : '#ff6b6b',
|
||||
'circle-stroke-width': 3,
|
||||
'circle-stroke-color': '#ffffff'
|
||||
}
|
||||
});
|
||||
|
||||
// Add overlay geometry if present
|
||||
if (site.overlay) {
|
||||
site.overlay.forEach((geom, idx) => {
|
||||
map.addSource(`site-overlay-${site.name}-${idx}`, {
|
||||
type: 'geojson',
|
||||
data: {
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [geom.coordinates]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: `site-overlay-${site.name}-${idx}`,
|
||||
type: 'fill',
|
||||
source: `site-overlay-${site.name}-${idx}`,
|
||||
paint: {
|
||||
'fill-color': site.type === 'road_confirmed' ? '#4A9EFF' : '#ff6b6b',
|
||||
'fill-opacity': 0.2
|
||||
}
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: `site-overlay-outline-${site.name}-${idx}`,
|
||||
type: 'line',
|
||||
source: `site-overlay-${site.name}-${idx}`,
|
||||
paint: {
|
||||
'line-color': site.type === 'road_confirmed' ? '#4A9EFF' : '#ff6b6b',
|
||||
'line-width': 2
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: 'octagon-fill',
|
||||
type: 'fill',
|
||||
source: 'octagon',
|
||||
paint: {
|
||||
'fill-color': '#ff0000',
|
||||
'fill-opacity': 0.2
|
||||
|
||||
// Add polyline for multi-coordinate sites
|
||||
if (site.coordinates.length > 1) {
|
||||
map.addSource(`site-polyline-${site.name}`, {
|
||||
type: 'geojson',
|
||||
data: {
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'LineString',
|
||||
coordinates: site.coordinates
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: `site-polyline-${site.name}`,
|
||||
type: 'line',
|
||||
source: `site-polyline-${site.name}`,
|
||||
paint: {
|
||||
'line-color': '#4A9EFF',
|
||||
'line-width': 3,
|
||||
'line-dasharray': [2, 2]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add click handler for marker popups
|
||||
map.on('click', `site-marker-${site.name}`, (e) => {
|
||||
e.preventDefault();
|
||||
popup.value = {
|
||||
visible: true,
|
||||
x: e.point.x,
|
||||
y: e.point.y,
|
||||
type: 'site',
|
||||
feature: {
|
||||
properties: {
|
||||
name: site.name,
|
||||
description: site.description,
|
||||
type: site.type
|
||||
}
|
||||
},
|
||||
lngLat: site.coordinates[0]
|
||||
};
|
||||
});
|
||||
|
||||
// Cursor pointer on hover
|
||||
map.on('mouseenter', `site-marker-${site.name}`, () => {
|
||||
map.getCanvas().style.cursor = 'pointer';
|
||||
});
|
||||
|
||||
map.on('mouseleave', `site-marker-${site.name}`, () => {
|
||||
map.getCanvas().style.cursor = '';
|
||||
});
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: 'octagon-outline',
|
||||
type: 'line',
|
||||
source: 'octagon',
|
||||
paint: {
|
||||
'line-color': '#ff0000',
|
||||
'line-width': 2
|
||||
|
||||
// Update popup position when map moves or zooms
|
||||
const updatePopupPosition = () => {
|
||||
if (popup.value.visible && popup.value.lngLat) {
|
||||
const point = map.project(popup.value.lngLat);
|
||||
popup.value.x = point.x;
|
||||
popup.value.y = point.y;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
map.on('move', updatePopupPosition);
|
||||
map.on('zoom', updatePopupPosition);
|
||||
|
||||
// Add geometry source and layers
|
||||
map.addSource('geometry', {
|
||||
@@ -1023,6 +1180,69 @@ onUnmounted(() => {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.control-section .section-header {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.subsection {
|
||||
margin-left: 10px;
|
||||
padding-left: 10px;
|
||||
border-left: 2px solid #ddd;
|
||||
}
|
||||
|
||||
.hide-all-btn {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
margin: 8px 0;
|
||||
background: #f5f5f5;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.hide-all-btn:hover {
|
||||
background: #e8e8e8;
|
||||
border-color: #999;
|
||||
}
|
||||
|
||||
.site-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.site-control label {
|
||||
flex: 1;
|
||||
margin: 0 !important;
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
.jump-btn {
|
||||
padding: 4px 8px;
|
||||
background: #4A9EFF;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.jump-btn:hover {
|
||||
background: #2E8FE3;
|
||||
}
|
||||
|
||||
.jump-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.control-section input[type="radio"],
|
||||
.control-section input[type="checkbox"] {
|
||||
margin-right: 8px;
|
||||
@@ -1153,6 +1373,7 @@ onUnmounted(() => {
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.4);
|
||||
z-index: 1000;
|
||||
min-width: 180px;
|
||||
max-width: 350px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -1173,6 +1394,24 @@ onUnmounted(() => {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.site-popup {
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.site-type {
|
||||
font-size: 11px !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: #999 !important;
|
||||
margin-bottom: 8px !important;
|
||||
}
|
||||
|
||||
.site-description {
|
||||
line-height: 1.5;
|
||||
color: #333 !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
.popup-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
||||
Reference in New Issue
Block a user