Monday, June 30, 2025

Take the 40 Bus Route Out Route 40

 After getting feedback, advice, and counsel about my recent public transport mapping updates in OpenStreetMaps, I started adding the missing "40" bus route to the Baltimore Metro Area. Part of the planning was whether to include bus stops and stopping locations, part was identifying errors and omissions in the OSM base, and part was being able to visualize current routes with OSM data.


Then a fragment:



I can see the 40 on existing bus stop signs:




This stop includes the OR (Orange), the 40, 59, 62, and 160 routes. When I looked, the OSM data for stop 7597 showed '59;62;160;OR' while the MTA showed '59;62;160;OR', as if the bus went to the Essex Park + Ride on the other side of the street instead of this stop.


So, I am unsure what the right data should be, until I ride the bus, or observe at the stop.

The guidance for adding a new route ("Step 1 - Make sure that each bus stop in the route has been added to OpenStreetMap") said check all the nodes. Given the above discrepancy, I hope to add at least one-way next, as small fixes could happen after, like when the routes/schedules change.

The last overview map update page from MTA [ mta.maryland.gov/transit-maps ] is from 2022, and the 40 route is not on the "BaltimoreLink Interactive System Map". That's a map from Google rather than hosted by the government. However, extrapolating shows this page is available in 2025:


Methodology

For myself, and others who may wish to get some GIS mojo working, general steps I've taken to gather data, analyze it, and visualize in various ways to find improvements to OSM content. The earlier post [ https://jspath55.blogspot.com/2025/06/which-bus-such-bus.html ] covers some of this; apologies for repeating myself.

MTA Data

The Maryland Department of Transportation includes the Mass Transit Administration. In addition to the schedule site listed above, there is a GIS portal with bus stops and routes, though I've not gotten much out of the latter.


Home > services > Transportation > MD_Transit (FeatureServer)


Then:


And end up here:



GIS ARC

Part 2 (right side)


The way to pull the data is via:

https://geodata.md.gov/imap/rest/services/Transportation/MD_Transit/FeatureServer/9/query

https://geodata.md.gov/imap/rest/services/Transportation/MD_Transit/FeatureServer/9/query?where=stop_id+is+NOT+NULL ...


"All" query: stop_id is NOT NULL

Output fields: stop_id, stop_name, Routes_Served, shelter

Results in a linear format:

# records: 4549

stop_name: Cylburn Ave & Greenspring Ave
Routes_Served: 31, 91, 94
Shelter: Yes
stop_id: 1
Point:
X: -8533795.9128
Y: 4772066.8330999985

stop_name: Lanier Ave & Sinai Hospital
Routes_Served: 31, 91, 94
Shelter: No
stop_id: 3
Point:
X: -8534126.0864
Y: 4772153.208300002

[...]

I parsed this into a CSV format to load into QGIS. The wrinkle was finding how to deal with the X Y coordinates returned rather than the expected latitude/longitude pairs. QGIS was happy when I set the coordinate reference system (CRS) to "ESRI:102100 - WGS_1984_Web_Mercator_Auxiliary_Sphere."

OSM data

I used OverPass-Turbo.eu to get extracts of bus stopping locations and bus stops, then separate them.

[out:csv("ref", "public_transport", "name", "route_ref", "highway", "network", "operator", ::lat, ::lon, ::id)];

(
  {{geocodeArea:"Maryland"}};

  )->.metroArea;

node(area)[bus=yes];
out;

I used "Maryland" rather than "Baltimore" to pick up Anne Arundel County and other nearby locales, though this expanded the return to include eastern and western counties as well as DC Metro. Better too much data than not enough here.

The first tries did not include position or OSM node ID; eventually I found the special names needed to get theses critical columns. After splitting into 2 files to allow QGIS layers for each, over 4,000 bus stops found though under 1,000 stopping locations.

$ wc -l OSM_platforms_20250623.csv OSM_stops_20250623.csv
  4240 OSM_platforms_20250623.csv
   928 OSM_stops_20250623.csv
  5168 total

Sample export:



Add these 2 layers, plus the MTA extract, and start the reconciliation.

Things found:

  • MTA and OSM sequence the routes differently, and the MTA records appear in yet another order compared to bus stop signs. I wrote a Perl script to convert the MTA data into the order OSM expects to make it easier to update wrong data.
  • The bus stop names differ in upper case/lower case, and various abbreviations ("nb" versus "northbound", for example.
  • Typos in both OSM and MTA data conglomerating 2 or more routes to one number ("78150" instead of "78, 150" or "78;150".
  • Null data nodes: apparent OSM bus stops without details (e.g. openstreetmap.org/node/2743861086 ). These are presumably orphans, though they may be incomplete nodes that should remain. If no MTA stops nearby, fix me candidates.
  • Typos of other sorts, missing stops, incomplete or duplicate descriptions.

Things fixed:

I alternated trying to simplify the updates to be made by only looking at platforms, but decided that version 2 with a stopping location paired with the platform is the preferred way. I created a spreadsheet to have a local index of the 40 bus routes in either direction. The columns included nodes for each type, stop numbers and names, and an alternate name per the official signage ("Eastpoint" as the area for Eastern & 54th, east or west bound).

Only a handful of bus stop areas include a stopping location, so that is my current task. After that, on to steps 2 and 3:

Step 2 - Create the new bus relations
Step 3 - Create a route master relation


The QGIS map view, overlaying MTA, OSM stops/platforms:



By migrating these layers into a PostGIS/PostgreSQL database (using a CRS under 10,000), I can now write SQL queries with outer joins and other comparison techniques.

Stops in MTA but not OSM:

select
  ref,
  substr(trim(route_ref),1,8) as routes,
  trim(name) as name
from
  "OSM_platforms_20250623"
where
  cast(ref as text) NOT in (
    select
      field_4
    from
      "bus-stops-20250611"
  )
;
[...]
   14166 | 30;34    | Northern Parkway & Park Heights Avenue Westbound Far-side
   14215 |          | Tradepoint Atlantic Floor & Decor
   14216 |          | Tradepoint Atlantic Royal Farms
  190036 |          | Baltimore Greyhound Terminal
 3002008 |          | Good Luck Rd at Oakland Ave
 3003202 |          | Good Luck Rd at Parkdale High School
 3003205 |          | Good Luck Rd at Oakland Ave
(133 rows)


Stops in MTA but not OSM:

select
      upper(substr(mta.field_1,1,40)) as nm,
      substr(mta.field_2,1,32) as f2,
      mta.field_4,
      osm.ref
from
      "bus-stops-20250611" mta
left outer join
      "osm_platforms_20250623_md" osm on mta.field_4 = cast(osm.ref as text)
where
  osm.ref is NULL
or
  cast(osm.ref as float) <= 0
order by
  cast(mta.field_4 as float)
;

This one included DC Metro areas, so needs more tuning to be useful.

Show mismatched route lists per stop:

select
      upper(substr(mta.name,1,40)) as nm,
      substr(mta.route_osm_style,1,32) as mta_route_osm,
      substr(osm.route_ref,1,32) as osm_route,
      mta.stop,
      osm.ref
from
      "bus-stops-20250611+0626" mta
left outer join
      "osm_platforms_20250623_md" osm on cast(mta.stop as text)  = osm.ref
where
  (osm.ref is NULL or cast(osm.ref as text) >= '0')
and
  mta.route_osm_style != osm.route_ref
order by
  mta.stop
;

The later version of the MTA list includes the reordered route list, as comparing the strings in SQL was not an obvious approach.  Example results:

[...]
      upper(substr(mta.name,1,40)) as nm,
      substr(mta.route_osm_style,1,32) as mta_route_osm,
      substr(osm.route_ref,1,32) as osm_route,
      mta.stop
[...]
LOCH RAVEN BLVD & STATE HWY DR SB 53 53;103;104 422
LOCH RAVEN BLVD & SAYWARD AVE SB 53;GR 53;103;104;GR 435
SAINT PAUL ST & 31ST ST SB 51;95;SV 95;SV 482
SAINT PAUL ST & 29TH ST SB 51;95;SV 95;SV 483
CHARLES ST & REDWOOD ST NB 51;65;67;95;103;410;411;420;GR;S 51;65;67;95;103;164;410;411;420; 506
SAINT PAUL ST & FAYETTE ST FS SB 67;76;95;103;120;210;215;310;410 67;76;95;103;120;164;210;215;310 516
CHARLES ST & HYATT NB 51;67;94;95;103;SV 51;67;94;95;103;103;164;SV 518
CHARLES ST & FAYETTE ST NB 51;95;103;410;411;420;GR;SV 51;95;103;410;411;420;425;GR;SV 519
CHARLES ST & READ ST NB 51;95;103;GR;SV 51;95;103;410;GR;SV 525

I photographed the bus stop sign at Charles and Read Streets (northbound) only yesterday, confirming the list MTA had. And updated the node: openstreetmap.org/node/6254842377

Charles + Read Streets Baltimore

U.S. Route 40, as opposed to MTA Bus Route 40, is the "National Pike"; see also wikipedia.org/wiki/Old_National_Pike.



Tuesday, June 10, 2025

Which Bus? Such bus.

 

It started innocently when I noticed a local transit shelter had been moved to the other side of the highway but that wasn't showing up in Open Street Maps. When I looked deeper, I found OSM had 1 shelter and 3 bus stop like nodes where the bus formerly stopped.

Because I was inexperienced at using OSM for public transport routing as opposed to vehicle directions, I knew I should be careful making edits less valid data or relationships be lost or confusing to others.

It later dawned on me that this wasn't 1 stop being removed, or moved, it was 2 stops replaced by one nearby. So I chose to delete the 3 bogons, and move the leftover down the road. I think I did it right and am waiting for various map sites to get these updates and see how they look.

But like pulling a loose thread that turns into an unravel-fest, I found more "errors and omissions" on other stops beyond where I started. Primary bugs:

  • Same bus stop ID number on 2 or more nodes. Should not happen.
  • Some stops had a route identifier; some did not.
  • Some stops had links/routes showing line numbers/names; some did not.
  • Nodes were classified as "bus" "bus stop" "bus station" willy-nilly
  • Multiple routes at one stop were mis-keyed as unseparated values
  • A new route is not on the map; does it exist?


Whispering Woods transit stop, in the area now known as Hopkins Point.



Bus stops seem duped.

Insert oops here

@neatnit@fosstodon.org Thank you for the guidance. Seems redundant stops were added by different people over time, and I was uncertain best approach. This stop was moved to the other side of the highway, so passengers go east one stop to a loop, then go west.


[out:json][timeout:25];
// gather results

/*
*/

node["highway"="bus_stop"]["ref"]({{bbox}});
for(t["ref"]) {
  if(count(nodes) > 1) {
    out tags center;
  }
}

// print results
out geom;


fix check:

https://overpass-api.de/api/sketch-line?network=BaltimoreLink&ref=59&operator=



The above is one view of the metro bus routes, showing the phantom (new) route #40.

It's ARCGIS, with Maryland State as the client/portal.

Another view:


Also ESRI-based, but with a more useful diagram of the route *and* stops. And termini.

https://www.mta.maryland.gov/schedule/stops/40

The MTA has a not-quite-current list of all bus stops, from a non-GUI menu path:

https://geodata.md.gov/imap/rest/services/Transportation/MD_Transit/FeatureServer/9/query

Simple-enough query-by-example fill-in-the boxes:


SELECT
stop_id, stop_name, Routes_Served, shelter
WHERE 
stop_id is NOT NULL

which returns columnar date in separate rows, plus coordinates.

e.g.

stop_name: OLD EASTERN AVE & SELIG AVE fs eb
Routes_Served: OR, 59, 62
Shelter: No
stop_id: 4522
Point:
X: -8511670.83
Y: 4766621.937700003

stop_name: OLD EASTERN AVE & ESSEX AVE eb
Routes_Served: OR, 59, 62, 160
Shelter: No
stop_id: 4524
Point:
X: -8511141.9511
Y: 4766737.762000002

(raw:)
/stop_name: / EASTERN BLVD & VIRGINIA AVE eb
/Routes_Served: / OR, 59, 62, 160
/Shelter: / No
/stop_id: / 7597
/Point:/
/X: / -8514337.1545
/Y: / 4765238.0484

The coordinate reference system I found worked with QGIS is "ESRI meters", also known as:


PROJCRS["WGS_1984_Web_Mercator_Auxiliary_Sphere",
    ID["ESRI",102100]]

I used the findings of the duplicate stop search to check location and routes, as well as naming standards (which someone took care of 99.4% not too many years ago).






The zip-ties are an indicator of datum freshness. Broken signboard likewise.

Next steps:

  • Add the missing 40 bus to OpenStreetMaps as a blank spot on the canvas.
  • Find duplicate stops by checking how close together they are, that aren't headed in opposite directions.
  • Figure out what to do about the majority of missing routes (as relations) for nodes.
  • Other errors and omissions as they appear, i.e. quality assurance.
  • Share timetable/schedule/interval info. See: chaos.social/@jspath55/114659131418285869
  • Compare recorded comfort features (benches/shelters) with ground conditions.








Saturday, May 31, 2025

Geotagging Charger Stations to see on Android Auto OsmAnd

 Searching for EV charging stations seems as much an art than a science so far. My flexible vehicle display provides searches from the manufacturer, and through Android Auto Google Maps and my installed OSMAND Android app.

What I'd like to see at a minimum I have through my own favorites shared as a KML file over Dropbox. Bit of work, and worth it so far for me.


I found when OsmAnd shows favorites, they are in alphabetical order, and no more than 10 are shown no matter how many are in the KML file. I've split my chargers list by brand in order to view all of them, even in the fractured level way. This example shows only Shell EV stations.

The map is useless, going from Hudson Bay to Venezuela, and there is no way to sort by distance, only name. Clicking on one item gets to navigation, with no map view in between.

Multiple chargers in one spot gets the worst result with OsmAnd Points of Interest for chargers.


You can see 10, but only 4 at once...

Some useful places to avoid, though, in later content.


At least on the POI view a map can appear. Not that you can zoom, pan, or otherwise affect that landscape shown behind the list while in motion. Or stopped at a light, as all of these shots were.

Only one field in the OpenStreetMap data is shown on the favorites list; thus I have refined what that should include, given space, mnemonic devices, and community standards. After I finish "just a couple more" on OSM edits I will reload my local phone map store and see what I've sown.

  


Tuesday, May 27, 2025

Bake-off - Mapillary and Panoramax

 Comparing 2 photo geolocation upload services, I considered dropping use of Mapillary if an equivalent portal exists beyond Google or Apple photos, first running into an incompatibility of graphic formats. The workaround was an un-edited image.

Panoramax authenticated through OpenStreetMap. 


4 strikes and out. Meanwhile the same image went into Mapillary in parallel.



After figuring out MS Paint edits mangled the GPS tags, the image stuck in Panoramax.





OpenStreetMap, local fork er spork off.

The panoramic image is off with only one in the sequence.

I uploaded an image with a license blurred, and Mapillary blurred it some more. In Panoramax, the plate was not blurred at all. 

The poll I ran heavily favored not using Mapillary. Including links to Mapillary on the main app/map page favors images on that site, though.


Other than the oddity of image-stretching, and the variation in blurring, both seem acceptable. I will try to use Panoramax with a few more EV charging stations, adding more useful details and missing stations.

Wednesday, April 9, 2025

Food Service Mapping

Food banks run by charitable organizations are a public service where private entities fill in where government runs short. Picking up, storing, and sharing food throws lifelines to those in need. I volunteered to create updated maps for Scouting America and decided to use QGis and related software tools.

(1) OSM

Going from the lowest level up, I added OpenStreetMaps (OSM). This is an easy drag-and-drop from the base QGIS sources into the current project. Depending on the desired result, I change the transparency from none to 50% more or less.

The standard OSM layer sources from openstreetmap.org. I've found, using tools like Viking, that variations on the main source can be used, and I prefer to try the Humanitarian one. Eventually I found the wiki page which let me set the feed, like this:

https://wiki.openstreetmap.org/wiki/Raster_tile_providers

https://a.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png

(2) Org

The second layer is the organization table. These have street addresses in the source, so I geo-located them with a Census Bureau tool that works via command line.

Once located, the QGIS data are converted to geometry. In a PostgreSQL database, I imported organization details, which had only street addresses not geo-data. To convert from the address to a lat/lon point I found a Census page that I could script.


$ lynx -dump "https://geocoding.geo.census.gov/geocoder/locations/address?street=10001%20Bird%20River%20Rd&state=md&zip=21220&benchmark=2020"  | grep Interpolate
   Interpolated Longitude (X) Coordinates: -76.432431362395
   Interpolated Latitude (Y) Coordinates: 39.356117523642

Ran the conversion steps and viewed points and polygons.  Once the latitude and longitude are set, the PostGIS function to make this into valid point(s) I used is:

UPDATE org.org SET geom = ST_SetSRID(ST_MakePoint(lng, lat), 4326);

(3) Tract shapes

Next level layer shows US Census tracts. I found a couple sources of these shape files, and used the Maryland State data.

https://www.census.gov/cgi-bin/geo/shapefiles/index.php?year=2020&layergroup=Census+Tracts


[omitting how to add this layer from ZIP or shape files]

To match the boundaries of the local district I wrote a filter, first including only Baltimore County tracts and next excluding specific tracts not in scope.

QGIS lets me save and load a filter definition, more or less a SQL code snippet.

<Query>"COUNTYFP"='005' and "tractce"  NOT IN (
        '400100',
        '400200',
        '400400',
[...]
        '494201',
        '494202',
        '980000',
        '980100',
        '980200',

        '9999999999'
)
</Query>

The blank line prior to the query end lets me run a unique sort to more quickly find gaps and add or subtract tracts.

(4) Unit Tracts Coverage

The coverage mapping uses a simple table with tract and unit/organizations. In order to connect these to the tracts I created a view.

Sunday, March 9, 2025

The tertiary phase: 3 Maps in One Vehicle

osm-and-auto-map

New to me EV has built-in GPS and navigation system. Android phone in car has GPS, and google map navigation routing system. Phone cannot access car GPS, to avoid duplication of effort. Car navigation map data might be aging, no updates without manual effort. Google maps keep personal placemarks, intentional or not.

Aim

Create a list of nearby charging stations with ratings, cost, owner/operator.

  1. Just use Google
  2. Use Google as little as possible (on an Android phone with GPS switched on)

For Option 2 I got 2 suggestions:

  1. - OSMand
  2. - @jspath55 Tried `Organic Maps`? @organicmaps 
    1. Uses OpenStreetMaps: https://organicmaps.app/

I could not get  Organic Maps to do Android Auto, so OSMand was left to try.

https://osmand.net/docs/technical/osmand-file-formats/osmand-kml/ shows the general path I took using Google Earth and QGIS as alternate ways to produce a set of points.



Web site said $40US to connect to Android Auto. 3 tiers: free, one-time, subscription. Free version could only load a limited number of "offline" local maps (which has not been my experience so far).

Details

Use Viking and/or QGIS to record my points of interest (charging spots).

  • Keep data in PostgreSQL database.
  • Edit/view via LibreOffice.
  • Export KML from QGIS, import to OSMand.

It has been a (rough) year since I worked on a town map, pulling in layers of county land info. Points and lines in a SQL structured database with QGIS access (R/W). First table create showed up a "non-geo". Icon was a table/sheet instead of dots, lines, or blobby GIS areas.

Second try was no better success than the first.

On the third try, I copied a table definition from an exported table that had geo-goo.

                        Table "public.chargers"

    Column    |         Type          | Collation | Nullable | Default

--------------+-----------------------+-----------+----------+---------

 station_id   | integer               |           | not null |

 address      | character varying(40) |           |          |

 zipcode      | integer               |           |          |

 brand        | character varying(40) |           |          |

 vendor_id    | integer               |           |          |

 grade        | character varying(12) |           |          |

 price        | double precision      |           |          |

 visited      | date                  |           |          |

 updated      | date                  |           |          |

 lat          | double precision      |           |          |

 lon          | double precision      |           |          |

 the_geometry | geometry(Point,4326)  |           |          |

 kwh          | double precision      |           |          |

 name         | character(40)         |           |          |

 note         | character(80)         |           |          |

Indexes:

    "chargers_pkey" PRIMARY KEY, btree (station_id)

Check constraints:

    "chargers_geometry_point_chk" CHECK (st_geometrytype(the_geometry) = 'ST_Point'::text OR the_geometry IS NULL)

Finally able to add points and see them on the map. So far, no dice adding data points from LO that show on the map.

Next step: export map layer of points as a KML file.

First import to test on Google Earth showed every location called "noname".

Some data fields peeked through as table-like sheets in Google Earth on a PC, but less structured on a tablet:


Load into OSMand from a network drive, and yes, places show up under my favorites. Added a "name" column to the underlying table fixed the "noname" issue.

Using the OSMand app as a driving map is slightly different than the car map or the Google map. The colors and details are clearer. Startup is more complex, but does work.


Oddities:

  1. Route going through driveways
  2. Non-connected road shows as "take the left fork"
  3. Stream flow direction


The above stream shows 2 directions, as if at the top of a hill, or mound (blue arrows on blue dashed line).

Next question(s)

Use OSM data format instead of KML.

Is location data still going off-device? Will this option change that (limit or redirect)? Seems a lot of on-device processing based on battery drain (or charge slipperiness) and heat.



Wednesday, December 25, 2024

Not the Top Ten Bengies List You're Looking For

 

The top ten list is traditional, though not guaranteed, as I learned this year. In prior years there were plenty of shows to see so the picking of the top 10 was challenging. I would know which movies I enjoyed and the others. 2024, though, was a challenge to find 10 movies we saw at the Drive-In. 

The list that follows is basically in chronological order, running from March to December,  practically 10 full months. We skipped the dusk-to-dawn shows, or we could have added another 8 or so to the list.


MARCH
Bob Marley One Love


JUNE
INVASION OF the body SNATCHERS. 



INVASION OF THE BODY SNATCHERS (1956)
THE MAN WHO KNEW TOO MUCH




Scooby-Doo Doo and the BARCS



JULY
LION KING 1994
Inside Out 2




SEPTEMBER
Scout Drive In Camp In





BEETLEJUICE BEETLEJUICE
LITTLE SHOP of HORRORS







DECEMBER
Wicked
RED one







In retrospect, One Love was a standout; Red One was better than anticipated, and Little Shop of Horrors survived the test of time. I saw BEETLEJUICE twice, almost 3 times., but missed the original when it ran. See you next year!