Add pulse animation for POI search
This commit is contained in:
@@ -5,7 +5,8 @@ import 'ol/ol.css';
|
|||||||
import Map from 'ol/Map.js';
|
import Map from 'ol/Map.js';
|
||||||
import { defaults as defaultControls } from 'ol/control.js';
|
import { defaults as defaultControls } from 'ol/control.js';
|
||||||
import View from 'ol/View.js';
|
import View from 'ol/View.js';
|
||||||
import { fromLonLat, toLonLat } from 'ol/proj.js';
|
import { fromLonLat, toLonLat, getPointResolution } from 'ol/proj.js';
|
||||||
|
import Overlay from 'ol/Overlay.js';
|
||||||
import LayerGroup from 'ol/layer/Group.js';
|
import LayerGroup from 'ol/layer/Group.js';
|
||||||
import VectorLayer from 'ol/layer/Vector.js';
|
import VectorLayer from 'ol/layer/Vector.js';
|
||||||
import VectorSource from 'ol/source/Vector.js';
|
import VectorSource from 'ol/source/Vector.js';
|
||||||
@@ -21,6 +22,8 @@ export default class MapComponent extends Component {
|
|||||||
|
|
||||||
mapInstance;
|
mapInstance;
|
||||||
bookmarkSource;
|
bookmarkSource;
|
||||||
|
searchOverlay;
|
||||||
|
searchOverlayElement;
|
||||||
|
|
||||||
setupMap = modifier((element) => {
|
setupMap = modifier((element) => {
|
||||||
if (this.mapInstance) return;
|
if (this.mapInstance) return;
|
||||||
@@ -57,6 +60,16 @@ export default class MapComponent extends Component {
|
|||||||
|
|
||||||
apply(openfreemap, 'https://tiles.openfreemap.org/styles/liberty');
|
apply(openfreemap, 'https://tiles.openfreemap.org/styles/liberty');
|
||||||
|
|
||||||
|
// Create Overlay for search pulse
|
||||||
|
this.searchOverlayElement = document.createElement('div');
|
||||||
|
this.searchOverlayElement.className = 'search-pulse';
|
||||||
|
this.searchOverlay = new Overlay({
|
||||||
|
element: this.searchOverlayElement,
|
||||||
|
positioning: 'center-center',
|
||||||
|
stopEvent: false, // Allow clicks to pass through
|
||||||
|
});
|
||||||
|
this.mapInstance.addOverlay(this.searchOverlay);
|
||||||
|
|
||||||
this.mapInstance.on('singleclick', this.handleMapClick);
|
this.mapInstance.on('singleclick', this.handleMapClick);
|
||||||
|
|
||||||
// Load places when map moves
|
// Load places when map moves
|
||||||
@@ -196,11 +209,28 @@ export default class MapComponent extends Component {
|
|||||||
const coords = toLonLat(event.coordinate);
|
const coords = toLonLat(event.coordinate);
|
||||||
const [lon, lat] = coords;
|
const [lon, lat] = coords;
|
||||||
|
|
||||||
// ... continue with normal OSM fetch logic ...
|
// Determine search radius based on whether we clicked a named feature
|
||||||
|
const searchRadius = selectedFeatureName ? 30 : 50;
|
||||||
|
|
||||||
|
// Show visual feedback (pulse)
|
||||||
|
if (this.searchOverlayElement) {
|
||||||
|
const view = this.mapInstance.getView();
|
||||||
|
const resolutionAtPoint = getPointResolution(
|
||||||
|
view.getProjection(),
|
||||||
|
view.getResolution(),
|
||||||
|
event.coordinate
|
||||||
|
);
|
||||||
|
const diameterInMeters = searchRadius * 2;
|
||||||
|
const diameterInPixels = diameterInMeters / resolutionAtPoint;
|
||||||
|
|
||||||
|
this.searchOverlayElement.style.width = `${diameterInPixels}px`;
|
||||||
|
this.searchOverlayElement.style.height = `${diameterInPixels}px`;
|
||||||
|
this.searchOverlay.setPosition(event.coordinate);
|
||||||
|
this.searchOverlayElement.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Fetch authoritative data via Overpass
|
// 2. Fetch authoritative data via Overpass
|
||||||
try {
|
try {
|
||||||
const searchRadius = selectedFeatureName ? 30 : 50;
|
|
||||||
let pois = await this.osm.getNearbyPois(lat, lon, searchRadius);
|
let pois = await this.osm.getNearbyPois(lat, lon, searchRadius);
|
||||||
|
|
||||||
// Sort by distance from click
|
// Sort by distance from click
|
||||||
@@ -263,6 +293,10 @@ export default class MapComponent extends Component {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch POIs:', error);
|
console.error('Failed to fetch POIs:', error);
|
||||||
|
} finally {
|
||||||
|
if (this.searchOverlayElement) {
|
||||||
|
this.searchOverlayElement.classList.remove('active');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -141,3 +141,31 @@ body {
|
|||||||
color: #666;
|
color: #666;
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Map Search Pulse Animation */
|
||||||
|
.search-pulse {
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid rgba(255, 204, 51, 0.8); /* Gold/Yellow to match markers */
|
||||||
|
background: rgba(255, 204, 51, 0.2);
|
||||||
|
position: absolute;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
pointer-events: none;
|
||||||
|
animation: pulse 1.5s infinite ease-out;
|
||||||
|
box-sizing: border-box; /* Ensure border is included in width/height */
|
||||||
|
display: none; /* Hidden by default */
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-pulse.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: translate(-50%, -50%) scale(0.8);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(-50%, -50%) scale(1.4);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user