mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
163 lines
3.9 KiB
Go
163 lines
3.9 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// PublicTransportService provides read-only access to precomputed public transport data
|
|
type PublicTransportService struct {
|
|
metadata map[string]interface{}
|
|
stops map[string]interface{}
|
|
raw map[string]interface{}
|
|
baseDir string
|
|
}
|
|
|
|
// NewPublicTransportService attempts to load enriched public transport JSON from data directory
|
|
func NewPublicTransportService(baseDir string) (*PublicTransportService, error) {
|
|
svc := &PublicTransportService{baseDir: baseDir}
|
|
|
|
// Try to read enriched JSON first
|
|
candidates := []string{
|
|
filepath.Join(baseDir, "bugulma_public_transport_enriched.json"),
|
|
filepath.Join(baseDir, "bugulma_public_transport.json"),
|
|
}
|
|
|
|
var found bool
|
|
for _, p := range candidates {
|
|
if _, err := os.Stat(p); err == nil {
|
|
b, err := ioutil.ReadFile(p)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read public transport file %s: %w", p, err)
|
|
}
|
|
|
|
var raw map[string]interface{}
|
|
if err := json.Unmarshal(b, &raw); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal public transport json %s: %w", p, err)
|
|
}
|
|
|
|
svc.raw = raw
|
|
|
|
// Extract metadata and common stops if present
|
|
if m, ok := raw["metadata"].(map[string]interface{}); ok {
|
|
svc.metadata = m
|
|
}
|
|
if cs, ok := raw["common_stops_directory"].(map[string]interface{}); ok {
|
|
svc.stops = cs
|
|
} else {
|
|
svc.stops = map[string]interface{}{}
|
|
}
|
|
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
// No JSON file found, return empty service with baseDir set so GTFS files can still be served
|
|
svc.raw = map[string]interface{}{}
|
|
svc.metadata = map[string]interface{}{}
|
|
svc.stops = map[string]interface{}{}
|
|
}
|
|
|
|
return svc, nil
|
|
}
|
|
|
|
// GetMetadata returns metadata extracted from the data file
|
|
func (s *PublicTransportService) GetMetadata() map[string]interface{} {
|
|
return s.metadata
|
|
}
|
|
|
|
// ListStops returns a map of stop key -> stop object
|
|
func (s *PublicTransportService) ListStops() map[string]interface{} {
|
|
return s.stops
|
|
}
|
|
|
|
// GetStop returns a stop by id (key in the common_stops_directory)
|
|
func (s *PublicTransportService) GetStop(id string) (interface{}, bool) {
|
|
st, ok := s.stops[id]
|
|
return st, ok
|
|
}
|
|
|
|
// Search stops by substring match in names (case-insensitive)
|
|
func (s *PublicTransportService) SearchStops(query string) map[string]interface{} {
|
|
out := map[string]interface{}{}
|
|
if query == "" {
|
|
return out
|
|
}
|
|
for k, v := range s.stops {
|
|
if k == query {
|
|
out[k] = v
|
|
continue
|
|
}
|
|
// try looking into name fields if present
|
|
if m, ok := v.(map[string]interface{}); ok {
|
|
if name, ok := m["name"].(string); ok {
|
|
if containsIgnoreCase(name, query) {
|
|
out[k] = v
|
|
continue
|
|
}
|
|
}
|
|
if nameEn, ok := m["name_en"].(string); ok {
|
|
if containsIgnoreCase(nameEn, query) {
|
|
out[k] = v
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// ReadGTFSFile returns file contents from the GTFS export folder if present
|
|
func (s *PublicTransportService) ReadGTFSFile(filename string) (string, error) {
|
|
p := filepath.Join(s.baseDir, "bugulma_gtfs_export", filename)
|
|
b, err := ioutil.ReadFile(p)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(b), nil
|
|
}
|
|
|
|
// Helper: case-insensitive substring
|
|
func containsIgnoreCase(src, sub string) bool {
|
|
return len(src) >= len(sub) && (stringIndexIgnoreCase(src, sub) >= 0)
|
|
}
|
|
|
|
func stringIndexIgnoreCase(s, substr string) int {
|
|
// naive implementation that converts to lower-case
|
|
sLower := []rune{}
|
|
subLower := []rune{}
|
|
for _, r := range s {
|
|
if 'A' <= r && r <= 'Z' {
|
|
r = r - 'A' + 'a'
|
|
}
|
|
sLower = append(sLower, r)
|
|
}
|
|
for _, r := range substr {
|
|
if 'A' <= r && r <= 'Z' {
|
|
r = r - 'A' + 'a'
|
|
}
|
|
subLower = append(subLower, r)
|
|
}
|
|
|
|
sStr := string(sLower)
|
|
subStr := string(subLower)
|
|
return indexOf(sStr, subStr)
|
|
}
|
|
|
|
func indexOf(s, substr string) int {
|
|
if substr == "" {
|
|
return 0
|
|
}
|
|
for i := 0; i+len(substr) <= len(s); i++ {
|
|
if s[i:i+len(substr)] == substr {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|