Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • geocontrib/geocontrib-frontend
  • ext_matthieu/geocontrib-frontend
  • fnecas/geocontrib-frontend
  • MatthieuE/geocontrib-frontend
4 results
Show changes
Showing
with 128 additions and 663 deletions
src/assets/img/default.png

15.1 KiB | W: 0px | H: 0px

src/assets/img/default.png

2.7 KiB | W: 0px | H: 0px

src/assets/img/default.png
src/assets/img/default.png
src/assets/img/default.png
src/assets/img/default.png
  • 2-up
  • Swipe
  • Onion skin
src/assets/img/geolocation-icon.png

12.3 KiB

src/assets/img/line.png

5.89 KiB | W: 0px | H: 0px

src/assets/img/line.png

3.28 KiB | W: 0px | H: 0px

src/assets/img/line.png
src/assets/img/line.png
src/assets/img/line.png
src/assets/img/line.png
  • 2-up
  • Swipe
  • Onion skin
src/assets/img/logo-geocontrib-large.png

13.8 KiB

src/assets/img/logo-geocontrib.png

13.8 KiB

src/assets/img/logo-large.png

27.4 KiB

src/assets/img/logo-neogeo-circle-inverted.png

47.1 KiB

src/assets/img/logo-neogeo-circle.png

40.5 KiB

src/assets/img/logo-neogeo.png

11.4 KiB

src/assets/img/logo.png

9.98 KiB

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="210mm"
height="297mm"
viewBox="0 0 744.09448819 1052.3622047"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="logo.svg">
<defs
id="defs4">
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter4343"
x="-0.0516"
width="1.1032"
y="-0.0516"
height="1.1032">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="11.51431"
id="feGaussianBlur4345" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="375"
inkscape:cy="520"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<rect
inkscape:export-ydpi="74.43"
inkscape:export-xdpi="74.43"
transform="matrix(1.0337351,-0.17155069,0.17155069,1.0337351,-49.111849,56.004698)"
y="259.4512"
x="83.736664"
height="465.6055"
width="465.6055"
id="rect4293"
style="opacity:0.46200005;fill:#000000;fill-opacity:1;stroke:none;stroke-width:49.23444748;stroke-linejoin:miter;stroke-miterlimit:22;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4343)" />
<rect
style="fill:#8adcec;fill-opacity:1;stroke:#000000;stroke-width:49.23444748;stroke-linejoin:miter;stroke-miterlimit:22;stroke-dasharray:none;stroke-opacity:1"
id="rect3336"
width="440.76556"
height="440.76556"
x="56.281677"
y="327.0433"
transform="matrix(0.98650797,-0.16371323,0.16371323,0.98650797,0,0)"
inkscape:export-xdpi="74.43"
inkscape:export-ydpi="74.43" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:226.91236877px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="196.70358"
y="579.59528"
id="text4138"
sodipodi:linespacing="125%"
inkscape:export-xdpi="74.43"
inkscape:export-ydpi="74.43"><tspan
sodipodi:role="line"
id="tspan4140"
x="196.70358"
y="579.59528"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:226.91288757px;line-height:125%;font-family:Alegreya;-inkscape-font-specification:'Alegreya Bold';text-align:start;writing-mode:lr-tb;text-anchor:start">Lab</tspan></text>
</g>
</svg>
src/assets/img/marker.png

13 KiB | W: 0px | H: 0px

src/assets/img/marker.png

7.45 KiB | W: 0px | H: 0px

src/assets/img/marker.png
src/assets/img/marker.png
src/assets/img/marker.png
src/assets/img/marker.png
  • 2-up
  • Swipe
  • Onion skin
src/assets/img/multiline.png

11.2 KiB

src/assets/img/multimarker.png

12.8 KiB

src/assets/img/multipolygon.png

9.69 KiB

src/assets/img/polygon.png

8.26 KiB | W: 0px | H: 0px

src/assets/img/polygon.png

4.03 KiB | W: 0px | H: 0px

src/assets/img/polygon.png
src/assets/img/polygon.png
src/assets/img/polygon.png
src/assets/img/polygon.png
  • 2-up
  • Swipe
  • Onion skin
window.addEventListener('load', function () {
// ---------------------------------------------------------------------------
// Suppression d'un ligne de formset
// ---------------------------------------------------------------------------
let removables = document.getElementsByClassName('remove-row');
function RemoveIt() {
let getter = this.getAttribute('id').replace('-REM', '')
let hidden_input_delete = document.getElementById(''.concat('id_', getter, '-DELETE'));
let current_row = document.getElementById(getter + '-ROW');
hidden_input_delete.value = 'checked';
current_row.style.display = 'none';
};
for (let i = 0; i < removables.length; i++) {
removables[i].addEventListener('click', RemoveIt, false);
}
// ---------------------------------------------------------------------------
// Ajout d'un ligne de formset
// ---------------------------------------------------------------------------
let add_buttons = document.getElementsByClassName('add_button');
function AddRow() {
let prefix = this.getAttribute('data-related-fieldset');
let total_form = document.getElementById('id_' + prefix + '-TOTAL_FORMS')
let form_idx = total_form.value;
// Injection de l'indice de la ligne et ajout au formset
let empty_tbody = document.getElementById(prefix + '-EMPTY_TBODY').innerHTML.replace(/__prefix__/g, form_idx);
console.log(empty_tbody);
let tbody = document.getElementById(prefix + '-TBODY').insertAdjacentHTML('beforeend', empty_tbody);
// Ajout d'un event ciblant la nouvelle ancre de suppression
let remove_field = document.getElementById(prefix + '-' + form_idx + '-REM');
remove_field.addEventListener('click', RemoveIt, false);
total_form.setAttribute("value", parseInt(form_idx) + 1);
// total_form.value++
};
for (let i = 0; i < add_buttons.length; i++) {
add_buttons[i].addEventListener('click', AddRow, false);
}
});
import L from "leaflet"
import "leaflet/dist/leaflet.css";
import flip from '@turf/flip'
import axios from "axios"
axios.defaults.headers.common['X-CSRFToken'] = (name => {
var re = new RegExp(name + "=([^;]+)");
var value = re.exec(document.cookie);
return (value != null) ? unescape(value[1]) : null;
})('csrftoken');
let map;
let dictLayersToLeaflet = {};
L.TileLayer.BetterWMS = L.TileLayer.WMS.extend({
onAdd: function (map) {
// Triggered when the layer is added to a map.
// Register a click listener, then do all the upstream WMS things
L.TileLayer.WMS.prototype.onAdd.call(this, map);
map.on('click', this.getFeatureInfo, this);
},
onRemove: function (map) {
// Triggered when the layer is removed from a map.
// Unregister a click listener, then do all the upstream WMS things
L.TileLayer.WMS.prototype.onRemove.call(this, map);
map.off('click', this.getFeatureInfo, this);
},
getFeatureInfo: function (evt) {
if (this.wmsParams.basemapId != undefined) {
const queryableLayerSelected = document.getElementById(`queryable-layers-selector-${this.wmsParams.basemapId}`).getElementsByClassName('selected')[0].innerHTML;
if (queryableLayerSelected.trim() === this.wmsParams.title.trim()) {
// Make an AJAX request to the server and hope for the best
var params = this.getFeatureInfoUrl(evt.latlng);
var showResults = L.Util.bind(this.showGetFeatureInfo, this);
axios.get(
window.proxy_url,
{
params: params,
//dataType: "json",
}
).then(response => {
let data=response.data;
var err = typeof data === 'object' ? null : data;
if (data.features || err) {
showResults(err, evt.latlng, data);
}
})
.catch(error => {
throw (error)
//xhr.status;
//xhr.responseText;
//console.log(status)
}
)
}
}
},
getFeatureInfoUrl: function (latlng) {
// Construct a GetFeatureInfo request URL given a point
var point = this._map.latLngToContainerPoint(latlng, this._map.getZoom());
var size = this._map.getSize(),
params = {
url: this._url,
request: 'GetFeatureInfo',
service: 'WMS',
// srs: this.wmsParams.srs,
srs: 'EPSG:4326',
// styles: this.wmsParams.styles,
// transparent: this.wmsParams.transparent,
version: this.wmsParams.version,
// format: this.wmsParams.format,
bbox: this._map.getBounds().toBBoxString(),
height: size.y,
width: size.x,
layers: this.wmsParams.layers,
query_layers: this.wmsParams.layers,
info_format: 'application/json'
};
params[params.version === '1.3.0' ? 'i' : 'x'] = Math.floor(point.x);
params[params.version === '1.3.0' ? 'j' : 'y'] = Math.floor(point.y);
return params;
},
showGetFeatureInfo: function (err, latlng, data) {
let content;
if (err) {
//console.log(err);
content = `
<h4>${this.options.title}</h4>
<p>Données de la couche inaccessibles</p>
`
L.popup({ maxWidth: 800 })
.setLatLng(latlng)
.setContent(content)
.openOn(this._map);
} else {
// Otherwise show the content in a popup
let contentLines = [];
let contentTitle;
if (data.features.length > 0) {
Object.entries(data.features[0].properties).forEach(entry => {
const [key, value] = entry;
if (key !== 'bbox') {
contentLines.push(`<div>${key}: ${value}</div>`);
}
})
contentTitle = `<h4>${this.options.title}</h4>`;
content = contentTitle.concat(contentLines.join(''));
L.popup({ maxWidth: 800 })
.setLatLng(latlng)
.setContent(content)
.openOn(this._map);
} /* else {
console.log('Pas de features trouvées pour cette couche');
} */
}
}
});
L.tileLayer.betterWms = function (url, options) {
return new L.TileLayer.BetterWMS(url, options);
};
const mapUtil = {
getMap: () => {
return map;
},
createMap: function (options) {
const {
lat,
lng,
mapDefaultViewCenter,
mapDefaultViewZoom,
zoom,
zoomControl = true,
} = options;
map = L.map('map', {
maxZoom: 18,
zoomControl: false,
}).setView(
[
!lat ? mapDefaultViewCenter[0] : lat,
!lng ? mapDefaultViewCenter[1] : lng,
],
!zoom ? mapDefaultViewZoom : zoom
);
if (zoomControl) {
L.control
.zoom({
zoomInTitle: 'Zoomer',
zoomOutTitle: 'Dézoomer',
position: 'topright',
})
.addTo(map);
}
L.control.scale().addTo(map);
return map;
},
addGeocoders: function(configuration){
let geocoder;
const geocoderLabel = configuration.SELECTED_GEOCODER.PROVIDER;
if (geocoderLabel) {
const LIMIT_RESULTS = 5;
if (
geocoderLabel === configuration.GEOCODER_PROVIDERS.ADDOK
) {
geocoder = L.Control.Geocoder.addok({ limit: LIMIT_RESULTS });
} else if (
geocoderLabel === configuration.GEOCODER_PROVIDERS.PHOTON
) {
geocoder = L.Control.Geocoder.photon();
} else if (
geocoderLabel === configuration.GEOCODER_PROVIDERS.NOMINATIM
) {
geocoder = L.Control.Geocoder.nominatim();
}
L.Control.geocoder({
placeholder: "Chercher une adresse...",
geocoder: geocoder,
}).addTo(map);
}
},
addLayers: function (layers, serviceMap, optionsMap) {
if (layers) {
layers.forEach((layer) => {
if (layer) {
const options = layer.options;
if (options) {
options.opacity = layer.opacity;
if (layer.schema_type === 'wms') {
let leafletLayer;
if (layer.queryable) {
options.title = layer.title;
leafletLayer = L.tileLayer
.betterWms(layer.service, options)
.addTo(map);
} else {
leafletLayer = L.tileLayer
.wms(layer.service, options)
.addTo(map);
}
dictLayersToLeaflet[layer.id] = leafletLayer._leaflet_id;
} else if (layer.schema_type === 'tms') {
const leafletLayer = L.tileLayer(layer.service, options).addTo(map);
dictLayersToLeaflet[layer.id] = leafletLayer._leaflet_id;
}
}
}
});
} else {
L.tileLayer(serviceMap, optionsMap).addTo(map);
}
},
// Remove the base layers (not the features)
removeLayers: function () {
map.eachLayer((leafLetlayer) => {
if (
Object.values(dictLayersToLeaflet).includes(leafLetlayer._leaflet_id)
) {
map.removeLayer(leafLetlayer);
}
});
dictLayersToLeaflet = {};
},
updateOpacity(layerId, opacity) {
const internalLeafletLayerId = dictLayersToLeaflet[layerId];
map.eachLayer((layer) => {
if (layer._leaflet_id === internalLeafletLayerId) {
layer.setOpacity(opacity);
}
});
},
updateOrder(layers) {
// First remove existing layers undefined
layers = layers.filter(function (x) {
return x !== undefined;
});
// First remove existing layers
map.eachLayer((leafLetlayer) => {
layers.forEach((layerOptions) => {
if (dictLayersToLeaflet[layerOptions.id] === leafLetlayer._leaflet_id) {
map.removeLayer(leafLetlayer);
}
});
});
dictLayersToLeaflet = {};
// Redraw the layers
this.addLayers(layers);
},
addFeatures: function (features, filter) {
let featureGroup = new L.FeatureGroup();
features.forEach((feature) => {
let filters = [];
if (filter) {
const typeCheck = filter.featureType && feature.properties.feature_type.slug === filter.featureType;
const statusCheck = filter.featureStatus && feature.properties.status.value === filter.featureStatus;
const titleCheck = filter.featureTitle && feature.properties.title.includes(filter.featureTitle);
filters = [typeCheck, statusCheck, titleCheck];
}
if (!filter || !Object.values(filter).some(val => val) || Object.values(filter).some(val => val) && filters.length && filters.every(val => val !== false)) {
const geomJSON = flip(feature.geometry);
const popupContent = this._createContentPopup(feature);
if (geomJSON.type === 'Point') {
L.circleMarker(geomJSON.coordinates, {
color: feature.properties.color,
radius: 4,
fillOpacity: 0.5,
weight: 3,
})
.bindPopup(popupContent)
.addTo(featureGroup);
} else if (geomJSON.type === 'LineString') {
L.polyline(geomJSON.coordinates, {
color: feature.properties.color,
weight: 3,
})
.bindPopup(popupContent)
.addTo(featureGroup);
} else if (geomJSON.type === 'Polygon') {
L.polygon(geomJSON.coordinates, {
color: feature.properties.color,
weight: 3,
fillOpacity: 0.5,
})
.bindPopup(popupContent)
.addTo(featureGroup);
}
}
});
map.addLayer(featureGroup);
return featureGroup;
},
addMapEventListener: function (eventName, callback) {
map.on(eventName, callback);
},
_createContentPopup: function (feature) {
let author = "";
if (feature.properties.creator) {
author = feature.properties.creator.full_name
? `<div>
Auteur : ${feature.properties.creator.first_name} ${feature.properties.creator.last_name}
</div>`
: feature.properties.creator.username ? `<div>Auteur: ${feature.properties.creator.username}</div>` : '';
}
return `
<h4>
<a href="${feature.properties.feature_url}">${feature.properties.title}</a>
</h4>
<div>
Statut : ${feature.properties.status.label}
</div>
<div>
Type : <a href="${feature.properties.feature_type_url}"> ${feature.properties.feature_type.title} </a>
</div>
<div>
Dernière mise à jour : ${feature.properties.updated_on}
</div>
${author}
`;
},
};
export { mapUtil }
\ No newline at end of file
export function fileConvertSize(aSize){
aSize = Math.abs(parseInt(aSize, 10));
const def = [[1, 'octets', 0], [1024, 'ko', 0], [1024*1024, 'Mo', 1], [1024*1024*1024, 'Go', 2], [1024*1024*1024*1024, 'To', 2]];
for (let i=0; i<def.length; i++) {
if (aSize<def[i][0]) {
return (aSize/def[i-1][0]).toFixed(def[i-1][2]) + ' ' + def[i-1][1];
}
}
}
export function fileConvertSizeToMo(aSize){
aSize = Math.abs(parseInt(aSize, 10));
const def = [1024*1024, 'Mo', 1];
return (aSize/def[0]).toFixed(def[2]);
}
/**
* Determines the likely field delimiter in a CSV string by analyzing the first few lines.
* The function counts the occurrences of common delimiters such as commas, semicolons, and tabs.
* The most frequently and consistently occurring delimiter across the sampled lines is chosen as the likely delimiter.
*
* @param {string} text - The CSV string to analyze for determining the delimiter.
* @returns {string|false} The most likely delimiter character if one can be determined, or false if none is found.
*/
export function determineDelimiter(text) {
const lines = text.split('\n').slice(0, 5); // Analyze the first 5 lines
const delimiters = [',', ';', '\t']; // List of possible delimiters
let delimiterCounts = new Map(delimiters.map(d => [d, 0])); // Initialize a map to count delimiter occurrences
// Count the occurrences of each delimiter in each line
lines.forEach(line => {
delimiters.forEach(delimiter => {
const count = line.split(delimiter).length - 1; // Count the occurrences of the delimiter in the line
delimiterCounts.set(delimiter, delimiterCounts.get(delimiter) + count); // Update the count in the map
});
});
let mostCommonDelimiter = '';
let maxCount = 0;
// Determine the most common delimiter
delimiterCounts.forEach((count, delimiter) => {
if (count > maxCount) {
mostCommonDelimiter = delimiter; // Set the most common delimiter found so far
maxCount = count; // Update the maximum count found so far
}
});
return mostCommonDelimiter || false; // Return the most common delimiter or false if none is found
}
/**
* Parses a CSV string into an array of rows, where each row is an array of fields.
* The function correctly handles multiline fields enclosed in double quotes, removes
* carriage return characters (\r) at the end of lines, and allows for different field
* delimiters.
*
* @param {string} text - The CSV string to be parsed.
* @param {string} delimiter - The field delimiter character (default is comma ',').
* @returns {Array<Array<string>>} An array of rows, each row being an array of fields.
*/
export function parseCSV(text, delimiter = ',') {
let rows = []; // This will hold the parsed rows
let row = []; // Temporary array to hold the fields of the current row
let field = ''; // Temporary string to hold the current field
let inQuotes = false; // Boolean to track whether we are inside quotes
for (let i = 0; i < text.length; i++) {
const char = text[i]; // Current character
if (char === '"' && text[i - 1] !== '\\') {
inQuotes = !inQuotes; // Toggle the inQuotes flag if not escaped
} else if (char === delimiter && !inQuotes) {
// If the current character is the delimiter and we are not inside quotes,
// add the field to the row and reset the field variable
row.push(field.replace(/\r$/, '')); // Remove trailing carriage return
field = '';
} else if (char === '\n' && !inQuotes) {
// If the current character is a newline and we are not inside quotes,
// add the field to the row, add the row to the list of rows,
// and reset the field and row variables
row.push(field.replace(/\r$/, '')); // Remove trailing carriage return
rows.push(row);
row = [];
field = '';
} else {
// If the current character is part of a field, add it to the field variable
field += char;
}
}
// After the loop, check if there's a remaining field or row to be added
if (field) {
row.push(field.replace(/\r$/, '')); // Remove trailing carriage return
rows.push(row);
}
return rows; // Return the parsed rows
}
/**
* Checks if the values in 'lon' and 'lat' columns are decimal numbers in the provided CSV data.
*
* @param {Array<string>} headers - The array of headers from the CSV file.
* @param {Array<Array<string>>} data - The CSV data as an array of rows, each row being an array of field values.
* @returns {boolean} True if 'lon' and 'lat' are found and their values are decimal numbers, false otherwise.
*/
export function checkLonLatValues(headers, data) {
const lonIndex = headers.indexOf('lon');
const latIndex = headers.indexOf('lat');
// Check if both 'lon' and 'lat' headers are found
if (lonIndex === -1 || latIndex === -1) {
return false;
}
// Function to check if a string is a decimal number
const isDecimal = (str) => !isNaN(str) && str.includes('.');
for (const row of data) {
// Check if 'lon' and 'lat' values are decimal numbers
if (!isDecimal(row[lonIndex]) || !isDecimal(row[latIndex])) {
return false;
}
}
return true;
}
/* required styles */
/* For input, we add the strong #map selector to avoid conflicts with semantic-ui */
#map .leaflet-control-geocoder {
border-radius: 4px;
background: white;
min-width: 26px;
min-height: 26px;
}
.leaflet-touch .leaflet-control-geocoder {
min-width: 30px;
min-height: 30px;
}
.leaflet-control-geocoder a,
.leaflet-control-geocoder .leaflet-control-geocoder-icon {
border-bottom: none;
display: inline-block;
}
.leaflet-control-geocoder .leaflet-control-geocoder-alternatives a {
width: inherit;
height: inherit;
line-height: inherit;
}
.leaflet-control-geocoder a:hover,
.leaflet-control-geocoder .leaflet-control-geocoder-icon:hover {
border-bottom: none;
display: inline-block;
}
.leaflet-control-geocoder-form {
display: none;
vertical-align: middle;
}
.leaflet-control-geocoder-expanded .leaflet-control-geocoder-form {
display: inline-block;
}
#map .leaflet-control-geocoder-form input {
font-size: 120%;
border: 0;
background-color: transparent;
width: 246px;
}
.leaflet-control-geocoder-icon {
border-radius: 4px;
width: 26px;
height: 26px;
border: none;
background-color: white;
background-image: url(./images/geocoder.svg);
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
}
.leaflet-touch .leaflet-control-geocoder-icon {
width: 30px;
height: 30px;
}
.leaflet-control-geocoder-throbber .leaflet-control-geocoder-icon {
background-image: url(./images/throbber.gif);
}
.leaflet-control-geocoder-form-no-error {
display: none;
}
#map .leaflet-control-geocoder-form input:focus {
outline: none;
}
.leaflet-control-geocoder-form button {
display: none;
}
.leaflet-control-geocoder-error {
margin-top: 8px;
margin-left: 8px;
display: block;
color: #444;
}
.leaflet-control-geocoder-alternatives {
display: block;
width: 272px;
list-style: none;
padding: 0;
margin: 0;
}
.leaflet-control-geocoder-alternatives-minimized {
display: none;
height: 0;
}
.leaflet-control-geocoder-alternatives li {
white-space: nowrap;
display: block;
overflow: hidden;
padding: 5px 8px;
text-overflow: ellipsis;
border-bottom: 1px solid #ccc;
cursor: pointer;
}
.leaflet-control-geocoder-alternatives li a,
.leaflet-control-geocoder-alternatives li a:hover {
width: inherit;
height: inherit;
line-height: inherit;
background: inherit;
border-radius: inherit;
text-align: left;
}
.leaflet-control-geocoder-alternatives li:last-child {
border-bottom: none;
}
.leaflet-control-geocoder-alternatives li:hover,
.leaflet-control-geocoder-selected {
background-color: #f5f5f5;
}
/* Custom style */
.leaflet-control-geocoder-icon {
border-radius: 4px;
width: 35px;
height: 35px;
}
#map .leaflet-control-geocoder-form input {
height: 35px;
}
.leaflet-control-geocoder-alternatives li:first-of-type {
border-top: 1px solid #ccc;
}
.leaflet-control-geocoder-address-item {
font-weight: 600;
}
.leaflet-control-geocoder-address-detail {
font-size: 12px;
font-weight: normal;
}
.leaflet-control-geocoder-address-context {
color: #666;
font-size: 12px;
font-weight: lighter;
}