Localizador de Tiendas · Madrid

Encuentra tu Cultiva más cercano

8 locales en Madrid con café de especialidad, WiFi y buenas vibras

☕ Todos 🌿 Terraza 📶 WiFi 🛍 Take Away ⭐ Valorados
📍 Buscando desde: Malasaña, Madrid
🏪 Tiendas encontradas: 5 dentro de 3 km
🗺 Mapa: Leaflet + OpenStreetMap (gratuito)
📐 Distancia: Haversine en cliente

Leyenda

Cultiva Café
Tu búsqueda
Zona 2km
© OpenStreetMap contributors · Leaflet

Código generado — mapas-geolocalizacion skill

TypeScript Leaflet 1.9 Nominatim
// geocoding.ts — Nominatim (gratis)
async function geocode(query: string) {
  const res = await fetch(
    `https://nominatim.openstreetmap.org/search`
    + `?q=${encodeURIComponent(query)}`
    + `&format=json&limit=1&countrycodes=es`,
    { headers: { "User-Agent": "CultivaCafe/1.0" } }
  );
  const [hit] = await res.json();
  return { lat: +hit.lat, lng: +hit.lon };
}

// Haversine — distancia en km entre dos puntos
function haversine(
  lat1: number, lng1: number,
  lat2: number, lng2: number
): number {
  const R = 6371;
  const dLat = (lat2-lat1) * Math.PI / 180;
  const dLng = (lng2-lng1) * Math.PI / 180;
  const a = Math.sin(dLat/2)**2
    + Math.cos(lat1*Math.PI/180)
    * Math.cos(lat2*Math.PI/180)
    * Math.sin(dLng/2)**2;
  return R * 2 * Math.atan2(
    Math.sqrt(a), Math.sqrt(1-a)
  );
}
// store-locator.ts — Leaflet + filtrado
async function findNearest(
  userAddr: string,
  stores: Store[],
  maxKm = 10, limit = 5
) {
  const { lat, lng } = await geocode(userAddr);

  return stores
    .map(s => ({
      ...s,
      distKm: haversine(lat, lng, s.lat, s.lng)
    }))
    .filter(s => s.distKm <= maxKm)
    .sort((a,b) => a.distKm - b.distKm)
    .slice(0, limit);
}

// Inicializar mapa Leaflet
const map = L.map("map")
  .setView([lat, lng], 14);

L.tileLayer(
  "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
  { attribution: "© OpenStreetMap" }
).addTo(map);

nearest.forEach((s, i) => {
  L.marker([s.lat, s.lng])
    .bindPopup(`<b>${s.name}</b><br>`
      + `${s.distKm.toFixed(1)} km`)
    .addTo(map);
});