---
title: Custom Points in OSW
tags:
    - Guide
    - External
    - Developer
    - OSW 0.3
---

## Custom Points in OSW

This guide shows how to add custom non-routable point features (like bus stops) to an OpenSidewalks dataset using the Custom Points entity type.

_For a list of all guides on the TCAT Wiki, refer to the [Guides List](../../guides-list/index.md)._{ .guides-list-ref }

---

### What are Custom Points?

Custom Points in OpenSidewalks are user-defined non-routable point features that extend the schema beyond the predefined Core Entities and Adjacent Entities point types. While OpenSidewalks includes many built-in point types like fire hydrants, benches, and street lamps, Custom Points let you add any custom feature!

Doing so is simple - just prefix `ext:` before the tags that are not in the OSW schema.

This approach allows for immediately adding custom data, preserving data from existing systems.

---

### Common Use Cases

Custom Points are perfect for adding:

- **Transit infrastructure**: Bus stops, public transport shelters
- **Accessibility features**: Accessible parking spaces, wheelchair-accessible entrances, elevators
- **Wayfinding aids**: Information kiosks, signage
- **Points of interest**: Water fountains, ATMs, restrooms

---

### Case Study: GTFS Bus Stops to OSW Custom Points

In this example, we'll convert bus stop data from a GTFS `stops.txt` file into OpenSidewalks Custom Points. This is a common scenario for transit agencies and cities wanting to integrate public transportation data with pedestrian network information.

#### Understanding the Source Data

A typical GTFS `stops.txt` file contains bus stop information like this:

```csv
stop_id,stop_code,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,location_type,parent_station
1001,1001,Main St & 1st Ave,Northbound stop,47.6062,-122.3321,,http://example.com/stops/1001,0,
1002,1002,Main St & 2nd Ave,Southbound stop,47.6065,-122.3325,,http://example.com/stops/1002,0,
```

#### The Target: OSW Custom Points

We want to convert each bus stop into an OpenSidewalks Custom Point that looks like this:

```json
{
    "type": "Feature",
    "geometry": {
        "type": "Point",
        "coordinates": [-122.3321, 47.6062]
    },
    "properties": {
        "_id": "bus_stop_1001",
        "ext:stop_id": "1001",
        "ext:stop_code": "1001",
        "ext:stop_name": "Main St & 1st Ave",
        "ext:stop_desc": "Northbound stop",
        "ext:stop_url": "http://example.com/stops/1001"
    }
}
```

---

### Step-by-Step Conversion Process

#### Step 1: Prepare Your Data

Start with your GTFS `stops.txt` file. You'll need these key fields:

- `stop_lat` and `stop_lon` (for coordinates)
- `stop_id` (for unique identification)
- Any other fields you want to preserve (name, description, etc.)

**Quality check your data:**

- Remove any stops with missing coordinates
- Ensure stop_id values are unique
- Verify coordinates are in decimal degrees (WGS84)

#### Step 2: Map Fields to OSW Structure

Create a mapping between your GTFS fields and OSW Custom Point properties:

| GTFS Field             | Goes in OSW as         | Notes                                               |
| :--------------------- | :--------------------- | :-------------------------------------------------- |
| `stop_lon`, `stop_lat` | `geometry.coordinates` | Longitude first, then latitude                      |
| `stop_id`              | `_id`                  | Create unique OSW ID (e.g., "bus_stop\_" + stop_id) |
| `stop_id`              | `ext:stop_id`          | Keep original ID for reference                      |
| `stop_name`            | `ext:stop_name`        | Preserve original name                              |
| `stop_desc`            | `ext:stop_desc`        | Optional description                                |
| `stop_code`            | `ext:stop_code`        | Display code, if different from ID                  |
| `stop_url`             | `ext:stop_url`         | Link to more information                            |

A note on the `_id` field: Every OSW entity needs a unique `_id` (string with at least one character). For bus stops, consider using a prefix like "bus_stop\_" + the original stop_id to avoid conflicts with other features. Letters, numbers, and underscores are all fine!

#### Step 3: Create the GeoJSON Structure

- **Required fields** like `_id` go directly in `properties`
- **Custom fields** get the `ext:` prefix and go in `properties`
- **Geometry** goes in the `geometry` section, not `properties`

Your Custom Points need to be formatted as GeoJSON Features within a FeatureCollection. Here's how the field mapping from Step 2 translates into actual JSON:

```json
{
    "$schema": "https://sidewalks.washington.edu/opensidewalks/0.3/schema.json",
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": [-122.3321, 47.6062] // stop_lon, stop_lat
            },
            "properties": {
                "_id": "bus_stop_1001", // Required OSW field
                "ext:stop_id": "1001", // Original GTFS stop_id
                "ext:stop_name": "Main St & 1st Ave", // Original GTFS stop_name
                "ext:stop_desc": "Northbound stop" // Original GTFS stop_desc
            }
        }
    ]
}
```

**Key Structure Points:**

- All of the GTFS data fields (except coordinates) go in the `properties` section
- Custom fields like `ext:stop_name` are the actual field names in your JSON
- The `ext:` prefix is part of the field name, not a folder structure

#### Step 4: Add Dataset Metadata

Include required OpenSidewalks metadata at the dataset level:

```json
{
    "$schema": "https://sidewalks.washington.edu/opensidewalks/0.3/schema.json",
    "type": "FeatureCollection",
    "dataSource": {
        "name": "Metro Transit GTFS",
        "license": "Public Domain"
    },
    "dataTimestamp": "2024-12-01T00:00:00Z",
    "features": [
        // ... your custom points here
    ]
}
```

---

### Conversion Tools

#### Option 1: Purpose-built Scripts

In the specific case of GTFS stops, a script such as this one created by TCAT, [gtfs-to-tdei-converter.ps1](https://github.com/TaskarCenterAtUW/tdei-tools/blob/main/utilities/gtfs-to-tdei-converter.ps1), can be used.

#### Option 2: Online JSON Tools

Use your spreadsheet software of choice to add `ext:` prefix to column headers, then convert CSV to GeoJSON:

- https://geojson.io/
- https://mapbox.github.io/csv2geojson/

---

### Validation and Quality Assurance

#### Check Your Output

Before using your Custom Points, verify:

1. **Valid GeoJSON**: Use geojson.io or a JSON validator
2. **Coordinate format**: Longitude first, then latitude
3. **Unique IDs**: Each `_id` appears only once
4. **Required fields**: Every Custom Point has an `_id` and valid coordinates
5. **Custom fields**: All additional data uses the `ext:` prefix

#### Common Issues and Fixes

| Problem              | Solution                                        |
| :------------------- | :---------------------------------------------- |
| Coordinates reversed | Longitude should be first: `[lng, lat]`         |
| Missing `_id` field  | Add unique identifier to each feature           |
| Invalid JSON         | Use a JSON validator to find syntax errors      |
| Coordinate precision | GTFS uses 6+ decimal places; preserve precision |

---

### Integration with Existing OSW Data

#### Manual Adding to Existing Datasets

To add Custom Points to an existing OpenSidewalks dataset:

1. Load your existing .osw file
2. Add your Custom Point features to the `features` array
3. Ensure all `_id` values remain unique across the entire dataset

#### Coordinate System Compatibility

OpenSidewalks uses WGS84 (EPSG:4326) coordinates in decimal degrees. GTFS data is already in this format, so no conversion is needed. If your source data uses a different coordinate system, you'll need to reproject it first.

---

### Best Practices

#### Fields

- Use descriptive `ext:` field names: `ext:stop_name` not `ext:name`
- Keep original field names when possible for easier maintenance
- Be consistent across all your Custom Points
- Remove unnecessary fields to keep file sizes manageable
