survos/place-map-bundle
Composer 安装命令:
composer require survos/place-map-bundle
包简介
Open-source place library (OpenStreetMap point/polygon places) + Symfony UX Map integration: clustered, thumbnail-on-zoom item markers
README 文档
README
An open-source place library + map view. Two things:
- Place library — a
Placeentity (a precise point, a general area as a GeoJSON polygon, or both), imported from OpenStreetMap/Nominatim withplace:import "Geneva, Switzerland". - Map rendering enhancement — a small Stimulus controller that adds clustered, thumbnail-on-zoom item markers to a Symfony UX Map (
ux_map()) map.
This bundle deliberately does not ship its own map component or its own Leaflet wrapper. Rendering — tiles, the place polygon, the place marker, InfoWindows — is entirely symfony/ux-map + a renderer bridge (e.g. symfony/ux-leaflet-map). This bundle only adds the place-library data layer and the one thing ux-map doesn't do out of the box: clustered markers whose icon changes (dot → photo thumbnail) as you zoom in.
Requirements
- PHP 8.5+, Symfony 8.1+
- Doctrine ORM (ships the
Placeentity) - The consuming app must itself require
symfony/ux-map+ a renderer bridge. This bundle's JS is written againstsymfony/ux-leaflet-mapspecifically (it grabs the raw Leaflet map/Linstance offux-map's ownconnectevent) — a different renderer bridge (e.g. Google Maps) will not work with the enhancement controller, though thePlacelibrary /PlaceMapBuildervalue-object conversion is renderer-agnostic. survos/imgproxy-bundle— optional. When installed, item thumbnails are signed imgproxy URLs (presettiny); without it,PlaceMapBuilder::resolveThumb()passes the source URL through unchanged.
Why this split (and not a bespoke Leaflet wrapper)
Symfony\UX\Map\Marker supports a per-marker extra payload explicitly documented for "greater flexibility... via a custom Stimulus controller" — but the installed Leaflet bridge (ux-leaflet-map) does not round-trip that payload back onto the Leaflet marker instance it creates, so it can't drive an icon swap on zoom. Rather than fork/patch the bridge, item (photo) markers are built directly in assets/controllers/place_map_enhance_controller.js, on the same raw Leaflet map instance ux-map exposes via its ux:map:connect event — full control over clustering and icon swapping, zero forked bridge code, and the place-library layer (polygon + place marker, a small fixed set) still goes through ux-map normally via PlaceMapBuilder.
Commands
| Command | What |
|---|---|
place:import "<query>" [--code=] [--label=] |
Search OpenStreetMap/Nominatim for <query>, store its point (and GeoJSON polygon boundary, when OSM has one) as a Place. --code defaults to a slug of <query>. Safe to re-run — updates the existing row. |
php bin/console place:import "Geneva, Switzerland" --code=geneva
Configuration
survos_place_map: # Required by Nominatim's usage policy — identify your app + a contact. nominatim_user_agent: '%env(default::PLACE_MAP_USER_AGENT)%'
# .env
PLACE_MAP_USER_AGENT="YourApp/1.0 (https://your-site.example; contact@your-site.example)"
Usage
Build ux-map value objects from the place library with PlaceMapBuilder, then render with ux_map(). Add the enhancement controller to the same element via the attributes array so it can merge onto ux-map's own data-controller and listen for its ux:map:connect event:
final class GenevaMapController extends AbstractController { public function __construct( private readonly PlaceRepository $places, private readonly PlaceMapBuilder $mapBuilder, ) {} #[Route('/geneva-map')] public function __invoke(): Response { $geneva = $this->places->find('geneva'); // Item (photo) markers — plain arrays, NOT ux-map Marker objects: the // enhance controller builds its own Leaflet markers from these (see README // "Why this split"). thumb is resolved through imgproxy when installed. $items = array_map( fn (Photo $p) => [ 'lat' => $p->lat, 'lng' => $p->lng, 'title' => $p->title, 'url' => $this->generateUrl('photo_show', ['id' => $p->id]), 'thumb' => $this->mapBuilder->resolveThumb($p->imageUrl), ], $this->photoRepository->findGeocoded(), ); return $this->render('geneva_map.html.twig', [ 'polygon' => $this->mapBuilder->polygonFor($geneva), 'marker' => $this->mapBuilder->markerFor($geneva), 'items' => $items, ]); } }
{# templates/geneva_map.html.twig #} {{ ux_map( center: polygon ? null : {lat: marker.position.latitude, lng: marker.position.longitude}, zoom: 12, fitBoundsToMarkers: true, polygons: polygon ? [polygon] : [], markers: marker ? [marker] : [], attributes: { style: 'height: 480px; width: 100%;', 'data-controller': survos_stimulus('place-map-bundle', 'place-map-enhance'), 'data-survos--place-map-bundle--place-map-enhance-items-value': items|json_encode, 'data-survos--place-map-bundle--place-map-enhance-thumbnail-zoom-value': 13, } ) }}
ux_map()'s own attributes['data-controller'] handling normalizes whatever string you pass the same way stimulus_controller() does — that's why survos_stimulus('place-map-bundle', 'place-map-enhance') (which returns the friendly @survos/place-map-bundle/place-map-enhance form, never hand-derived) works directly there. The Stimulus value attribute names, however, are passed straight through unnormalized, so they must spell out the resolved identifier (survos--place-map-bundle--place-map-enhance) explicitly — the same identifier survos_stimulus()'s output normalizes to.
Data model
Place (table place_map_place): code (PK), label, lat/lng (nullable), geojson (nullable — a GeoJSON Polygon/MultiPolygon), boundingBox (nullable [south, north, west, east], cheap for map fit-bounds without parsing geojson), source/sourceId (provenance, e.g. osm / relation/1685488), meta (free-form, e.g. OSM's display_name/address parts).
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 0
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-07-13