Skip to content

Commit

Permalink
Ajoute une recherche de lieu à la carte
Browse files Browse the repository at this point in the history
  • Loading branch information
florimondmanca committed Oct 1, 2024
1 parent f3b46ca commit 9e5dac4
Show file tree
Hide file tree
Showing 12 changed files with 223 additions and 2 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ APP_BAC_IDF_CITIES_FILE=data/bac_idf/cities.csv
DATABASE_URL="postgresql://dialog:dialog@database:5432/dialog"
REDIS_URL="redis://redis:6379"
API_ADRESSE_BASE_URL=https://api-adresse.data.gouv.fr
APP_IGN_GEOCODER_BASE_URL=https://data.geopf.fr
MATOMO_ENABLED=false
###> BD TOPO ###
BDTOPO_DATABASE_URL=
Expand Down
1 change: 1 addition & 0 deletions assets/customElements/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ import './auto_form';
import './modal_trigger';
import './map';
import './map_form';
import './map_search_form';
25 changes: 24 additions & 1 deletion assets/customElements/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,14 @@ class MapLibreMap {
});
}

/**
* Center map on a given location.
* @param {[number, number]} coordinates
*/
flyTo(coordinates) {
this.#map.flyTo({ center: coordinates });
}

/**
*
* @param {[number, number]} pos
Expand Down Expand Up @@ -257,7 +265,7 @@ class MapLibreMap {

const METROPOLITAN_FRANCE_CENTER = '[2.725, 47.16]';

class MapElement extends HTMLElement {
export class MapElement extends HTMLElement {
connectedCallback() {
const mapHeight = this.getAttribute('mapHeight') || '100%';
const mapMinHeight = this.getAttribute('mapMinHeight') || '600px';
Expand Down Expand Up @@ -306,6 +314,21 @@ class MapElement extends HTMLElement {

observer.observe(this, { attributes: true });
}

/**
* Center map on given coordinates
* @param {[number, number]} coordinates
* @param {number} zoom
*/
flyTo(coordinates, zoom) {
this.map?.flyTo({
center: coordinates,
zoom,
// Animation options
duration: 2000, // ms
essential: false, // Disable if browser has [prefers-reduced-motion]
});
}
}

customElements.define('d-map', MapElement);
25 changes: 25 additions & 0 deletions assets/customElements/map_search_form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// @ts-check

import { getAttributeOrError, querySelectorOrError } from "./util";
import { MapElement } from './map';

customElements.define('d-map-search-form', class extends HTMLElement {
connectedCallback() {
requestAnimationFrame(() => {
/** @type {MapElement} */
const map = querySelectorOrError(document, `#${getAttributeOrError(this, 'target')}`);

/** @type {HTMLInputElement} */
const selectedValueField = querySelectorOrError(document, `#${getAttributeOrError(this, 'selectedValueFieldName')}`);

selectedValueField.addEventListener('change', () => {
const { coordinates, kind } = JSON.parse(selectedValueField.value);

// Zoom closer on streets
const zoom = kind === 'street' ? 15 : 12;

map.flyTo(coordinates, zoom);
});
});
}
});
2 changes: 2 additions & 0 deletions config/packages/framework.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ framework:
base_uri: '%env(APP_EUDONET_PARIS_BASE_URL)%'
litteralis.wfs.http.client:
base_uri: '%env(APP_LITTERALIS_WFS_BASE_URL)%'
ign.geocoder.client:
base_uri: '%env(APP_IGN_GEOCODER_BASE_URL)%'

when@test:
framework:
Expand Down
10 changes: 10 additions & 0 deletions src/Application/MapGeocoderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace App\Application;

interface MapGeocoderInterface
{
public function findPlaces(string $search): array;
}
47 changes: 47 additions & 0 deletions src/Infrastructure/Adapter/IgnMapGeocoder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace App\Infrastructure\Adapter;

use App\Application\MapGeocoderInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

final class IgnMapGeocoder implements MapGeocoderInterface
{
public function __construct(
private HttpClientInterface $ignGeocoderClient,
) {
}

public function findPlaces(string $search): array
{
$response = $this->ignGeocoderClient->request(
'GET',
'/geocodage/completion',
[
'query' => [
'text' => $search,
'type' => 'StreetAddress, PositionOfInterest',
'poiType' => 'administratif',
],
],
);

$data = json_decode($response->getContent(), true);

$places = [];

foreach ($data['results'] as $result) {
$places[] = [
'label' => $result['fulltext'],
'value' => [
'coordinates' => [$result['x'], $result['y']],
'kind' => $result['kind'],
],
];
}

return $places;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace App\Infrastructure\Controller\Map\Fragments;

use App\Application\MapGeocoderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;

final class MapSearchController
{
public function __construct(
private \Twig\Environment $twig,
private MapGeocoderInterface $mapGeocoder,
) {
}

#[Route(
'/carte/search',
name: 'fragment_carto_search',
methods: ['GET'],
)]
public function __invoke(Request $request): Response
{
$search = $request->query->get('search');

if (!$search) {
throw new BadRequestHttpException();
}

$results = $this->mapGeocoder->findPlaces($search);

return new Response(
$this->twig->render(
name: 'map/fragments/search_results.html.twig',
context: [
'results' => $results,
],
),
);
}
}
47 changes: 47 additions & 0 deletions templates/map/_search_form.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<div
class="fr-x-autocomplete-wrapper"
data-controller="autocomplete"
data-autocomplete-url-value="{{ path('fragment_carto_search') }}"
data-autocomplete-query-param-value="search"
data-autocomplete-min-length-value="3"
data-autocomplete-delay-value="500"
data-autocomplete-loading-status-value="{{ 'common.autocomplete.status.loading'|trans }}"
data-autocomplete-empty-status-value="{{ 'common.autocomplete.status.min_chars'|trans({ '%minChars%': 3 }) }}"
data-action="autocomplete.change->reset#reset"
>
<div class="fr-search-bar">
<label for="map_search" class="app-sr-only">
{{ 'map.search_form.search'|trans }}
</label>

<input
id="{{ selectedValueFieldName }}"
name="{{ selectedValueFieldName }}"
type="hidden"
data-autocomplete-target="hidden"
>

<input
id="search"
name="search"
type="text"
class="fr-input"
spellcheck="false"
autocomplete="false"
data-autocomplete-target="input"
placeholder="{{ 'map.search_form.search.placeholder'|trans }}"
>

<button class="fr-btn" aria-label="{{ 'common.form.search'|trans }}">{{ 'common.form.search'|trans }}</button>
</div>

<ul
id="map_search-results"
role="listbox"
aria-label="{{ 'map.search_form.results_label'|trans }}"
class="fr-x-autocomplete"
data-autocomplete-target="results"
>
<li role="status" data-autocomplete-target="status"></li>
</ul>
</div>
7 changes: 7 additions & 0 deletions templates/map/fragments/search_results.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% for result in results %}
<li role="option" data-autocomplete-value="{{ result.value|json_encode }}">{{ result.label }}</li>
{% endfor %}

<template id="status">
{{ 'common.autocomplete.results_count'|trans({ '%count%': results|length }) }}
</template>
7 changes: 6 additions & 1 deletion templates/map/map.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@
<div class="fr-grid-row fr-x-h-full">
<div class="fr-col-12 fr-col-sm-4 fr-col-md-3 fr-container fr-pt-2w fr-pb-6w">
<h1 class="fr-h4">{{ 'map.title'|trans }} </h1>
<h2 class="fr-h6">{{ 'map.filters.title'|trans }}</h2>

<d-map-search-form target="map" selectedValueFieldName="selectedValue">
{% include 'map/_search_form.html.twig' with {'selectedValueFieldName': 'selectedValue'} only %}
</d-map-search-form>

<h2 class="fr-h6 fr-mt-2w">{{ 'map.filters.title'|trans }}</h2>

<d-map-form target="map" urlAttribute="dataUrl">
{{ form_start(form, {
Expand Down
8 changes: 8 additions & 0 deletions translations/messages.fr.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -2068,6 +2068,14 @@
<source>map.loading</source>
<target>Chargement de la carte en cours...</target>
</trans-unit>
<trans-unit id="map.search_form.search">
<source>map.search_form.search</source>
<target>Rechercher un lieu</target>
</trans-unit>
<trans-unit id="map.search_form.search.placeholder">
<source>map.search_form.search.placeholder</source>
<target>Rechercher un lieu</target>
</trans-unit>
<trans-unit id="map.filters.title">
<source>map.filters.title</source>
<target>Filtres</target>
Expand Down

0 comments on commit 9e5dac4

Please sign in to comment.