OneBusAway / wayfinder

Modern web app frontend for OneBusAway built in JavaScript with SvelteKit
GNU Affero General Public License v3.0
4 stars 1 forks source link

Add Support for OpenStreetMap #48

Closed aaronbrethorst closed 2 weeks ago

aaronbrethorst commented 1 month ago

User story

as a transit agency operator, I want to use OpenStreetMap instead of Google Maps, to cut costs and increase flexibility

Acceptance Criteria

Explanation

The plugin architecture would center around creating a common interface that all map provider plugins must implement. This interface would define methods for essential map operations like initializing the map, setting the view, adding markers, and handling user interactions. You'd create a base class that outlines these required methods.

To implement a specific map provider, you'd create a new class that extends this base class or implements the interface. For example, you might have an OpenStreetMapProvider class and a GoogleMapProvider class. Each of these would implement the required methods using the specific API calls and conventions of their respective mapping libraries. The rest of your application would then interact with maps through this common interface, without needing to know the details of the specific provider being used.

To switch between providers, you'd simply need to instantiate the appropriate provider class and pass it to the components that need map functionality. You could even make this configurable, allowing users to choose their preferred map provider through a settings menu. This architecture promotes modularity and makes it easier to add support for new map providers in the future without having to modify the core application logic.

Example code

// Define the base class for map providers
class MapProvider {
  initMap(elementId) {
    throw new Error('initMap must be implemented');
  }

  setView(lat, lon, zoom) {
    throw new Error('setView must be implemented');
  }

  addMarker(lat, lon, popupText) {
    throw new Error('addMarker must be implemented');
  }
  // Add other common methods as needed
}

// OpenStreetMap implementation
class OpenStreetMapProvider extends MapProvider {
  constructor() {
    super();
    this.map = null;
  }

  initMap(elementId) {
    this.map = L.map(elementId); // Using Leaflet.js for OSM
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(this.map);
  }

  setView(lat, lon, zoom) {
    this.map.setView([lat, lon], zoom);
  }

  addMarker(lat, lon, popupText) {
    L.marker([lat, lon]).addTo(this.map).bindPopup(popupText);
  }
}

// Google Maps implementation
class GoogleMapProvider extends MapProvider {
  constructor() {
    super();
    this.map = null;
  }

  initMap(elementId) {
    this.map = new google.maps.Map(document.getElementById(elementId), {
      center: { lat: 0, lng: 0 },
      zoom: 8
    });
  }

  setView(lat, lon, zoom) {
    this.map.setCenter({ lat, lng: lon });
    this.map.setZoom(zoom);
  }

  addMarker(lat, lon, popupText) {
    new google.maps.Marker({
      position: { lat, lng: lon },
      map: this.map,
      title: popupText
    });
  }
}

// Usage in a Svelte component
export default {
  props: ['mapProvider'],

  onMount() {
    const { mapProvider } = this.props;
    mapProvider.initMap('map-container');
    mapProvider.setView(51.505, -0.09, 13);
    mapProvider.addMarker(51.5, -0.09, 'Hello, London!');
  }
};

// In your HTML
// <div id="map-container" style="height: 400px;"></div>

// In your main app or settings
const useOpenStreetMap = true;
const mapProvider = useOpenStreetMap ? new OpenStreetMapProvider() : new GoogleMapProvider();

// Pass mapProvider to your map component
aaronbrethorst commented 2 weeks ago

fixed in #50