Sat Nov 08 Estimated time: PT60M

How to Create a Routing App Coverage File

Learn how to build and submit a routing app coverage file for iOS. A complete guide to GeoJSON coverage areas, App Store configuration, and integration with Apple Maps for transit and navigation apps.

Routing app coverage file for iOS

What Is a Routing App Coverage File

A routing app coverage file is a GeoJSON document that tells Apple Maps where your app can provide directions. When a user requests transit, driving, walking, or cycling directions in Apple Maps, iOS checks the coverage files of all installed routing apps to determine which ones can handle the route. If your app covers the requested area, it appears as a routing option alongside Apple’s built-in directions.

This system was introduced in iOS 9 and applies to any app that provides turn-by-turn navigation, public transit directions, ride-hailing routing, cycling routes, or pedestrian navigation. Without a coverage file, your app will never appear in Apple Maps regardless of how good your routing engine is.

For App Store Optimization, the routing app integration is a significant discovery channel. Users who find your app through Apple Maps have extremely high intent - they need directions right now. This makes the routing integration one of the highest-converting organic channels available to navigation and transit apps.

Who Needs a Coverage File

You need a routing app coverage file if your app:

  • Provides public transit directions (bus, subway, train, ferry schedules)
  • Offers turn-by-turn driving navigation
  • Provides cycling route planning
  • Offers pedestrian walking directions
  • Provides ride-hailing routing (showing routes for car services)
  • Delivers any kind of point-to-point navigation that users might request through Maps

If your app only displays maps without routing (e.g., a map viewer or location tracker), you do not need a coverage file.

Step 1: Understand the Coverage File Requirements

Before building anything, understand exactly what Apple expects and how the system works.

How Apple Maps Uses Coverage Files

When a user requests directions in Apple Maps:

  1. Apple Maps determines the geographic area of the route (start point, end point, and intermediate area)
  2. It checks the coverage files of all installed apps that support routing
  3. Apps whose coverage area includes the route endpoints appear as routing options
  4. The user selects your app, and Apple Maps sends a routing request to it
  5. Your app opens and displays the directions

Your coverage file is the gatekeeper for step 2. If your GeoJSON polygons don’t include the user’s requested route, your app won’t appear even if it could technically handle the request.

Supported Routing Modes

Apple Maps supports these routing modes, defined in your Info.plist:

ModeInfo.plist KeyUse case
TransitMKDirectionsModesTransitBus, subway, train, ferry
AutomobileMKDirectionsModesAutomobileDriving navigation
PedestrianMKDirectionsModesWalkWalking directions
CyclingMKDirectionsModeBicycleBike route planning
Ride shareMKDirectionsModesRideShareRide-hailing services

Your coverage file can specify different routing modes for different geographic areas. For example, your app might support transit routing in 5 cities but cycling routing nationwide.

File Format

The coverage file must be valid GeoJSON conforming to RFC 7946. Apple accepts:

  • Feature: A single polygon or multipolygon with properties
  • FeatureCollection: Multiple features, each with their own geometry and properties
  • File size limit: 20 MB (though smaller files process faster)
  • Coordinate system: WGS 84 (EPSG:4326), the standard for GPS coordinates
  • File extension: .geojson

Step 2: Set Up Your Xcode Project

Your app needs the correct capabilities and Info.plist configuration before Apple Maps will recognize it as a routing provider.

Adding the Maps Capability

In Xcode:

  1. Select your app target
  2. Go to Signing & Capabilities
  3. Click ”+ Capability”
  4. Add “Maps”
  5. Check the routing modes your app supports (Transit, Automobile, Pedestrian, Cycling)

This adds the com.apple.developer.maps entitlement to your app.

Info.plist Configuration

Add the routing mode declarations to your Info.plist:

<key>MKDirectionsApplicationSupportedModes</key>
<array>
    <string>MKDirectionsModesTransit</string>
    <string>MKDirectionsModesAutomobile</string>
</array>

Only declare modes your app actually supports. If you declare transit mode but your app doesn’t provide transit directions, Apple will reject your submission.

URL Scheme for Routing Requests

Your app receives routing requests through a URL scheme. Register the directions URL type in your Info.plist:

<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeName</key>
        <string>MKDirectionsRequest</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>com.apple.maps.directionsrequest</string>
        </array>
    </dict>
</array>

This tells iOS that your app can handle MKDirectionsRequest objects.

Project Structure

Organize your project to separate routing logic from the rest of your app:

YourApp/
  Routing/
    RoutingRequestHandler.swift    - Parses incoming MKDirectionsRequest
    DirectionsViewController.swift - Displays route to the user
    CoverageManager.swift          - Manages coverage area data
  Resources/
    coverage.geojson               - Your routing coverage file

Step 3: Build the GeoJSON Coverage File

The coverage file defines the geographic boundaries where your app can provide directions. Precision matters - an area too small means you miss users who could use your app, an area too large means users select your app for routes you can’t handle.

GeoJSON Structure

A minimal coverage file with a single coverage area:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "name": "Dublin Metro Area",
        "modes": ["MKDirectionsModesTransit"]
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [[
          [-6.4, 53.2],
          [-6.0, 53.2],
          [-6.0, 53.45],
          [-6.4, 53.45],
          [-6.4, 53.2]
        ]]
      }
    }
  ]
}

Coordinate Format

GeoJSON uses longitude-latitude order (not latitude-longitude, which is the opposite of what most people expect):

[longitude, latitude]
[-6.2603, 53.3498]  // Dublin city center

This is the single most common mistake in coverage files. If your polygons appear on the wrong side of the world when you visualize them, you have the coordinates swapped.

Polygon Construction

Simple rectangle: For a city or metropolitan area, a bounding rectangle is the easiest starting point:

{
  "type": "Polygon",
  "coordinates": [[
    [-6.45, 53.20],
    [-6.05, 53.20],
    [-6.05, 53.45],
    [-6.45, 53.45],
    [-6.45, 53.20]
  ]]
}

The first and last coordinate must be identical to close the ring.

Detailed boundary: For precise coverage that follows city limits, transit network boundaries, or service area borders, use more coordinate points:

{
  "type": "Polygon",
  "coordinates": [[
    [-6.385, 53.285],
    [-6.350, 53.270],
    [-6.280, 53.260],
    [-6.200, 53.265],
    [-6.140, 53.290],
    [-6.100, 53.320],
    [-6.095, 53.360],
    [-6.110, 53.400],
    [-6.170, 53.420],
    [-6.260, 53.430],
    [-6.340, 53.415],
    [-6.390, 53.380],
    [-6.400, 53.340],
    [-6.385, 53.285]
  ]]
}

MultiPolygon for non-contiguous areas: If your app covers multiple separate regions, use a MultiPolygon or multiple features:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "name": "Dublin",
        "modes": ["MKDirectionsModesTransit"]
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [[ ... ]]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "name": "Cork",
        "modes": ["MKDirectionsModesTransit"]
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [[ ... ]]
      }
    }
  ]
}

Generating Coverage Boundaries

For transit apps, your coverage area should match your transit data coverage. Here are approaches for generating accurate boundaries:

From GTFS data: If you use GTFS (General Transit Feed Specification) data, extract the convex hull or bounding area of all stop locations:

import json
from shapely.geometry import MultiPoint, mapping

# Load stop coordinates from GTFS stops.txt
stops = [(stop_lon, stop_lat) for stop_lon, stop_lat in stop_data]

# Create a convex hull with a small buffer
coverage = MultiPoint(stops).convex_hull.buffer(0.01)

# Convert to GeoJSON
geojson = {
    "type": "FeatureCollection",
    "features": [{
        "type": "Feature",
        "properties": {"name": "Transit Coverage", "modes": ["MKDirectionsModesTransit"]},
        "geometry": mapping(coverage)
    }]
}

with open("coverage.geojson", "w") as f:
    json.dump(geojson, f)

From administrative boundaries: Use open data sources like OpenStreetMap, Natural Earth, or government boundary datasets to get precise city or region boundaries. Simplify complex boundaries to reduce file size while maintaining accuracy.

Manual drawing: Use geojson.io, Google Earth, or QGIS to manually draw your coverage area on a map and export as GeoJSON. This is practical for small coverage areas or when you need human judgment to define boundaries.

Coverage Area Sizing Strategy

Start slightly larger than your actual coverage. If your transit data covers routes within Dublin city limits, extend your coverage polygon 5-10 km beyond the city boundary. This ensures that users requesting routes from suburban areas just outside the city still see your app as an option.

Don’t cover areas you can’t serve. If a user selects your app for a route outside your actual service area and your app can’t provide directions, the user experience is terrible. They have to go back to Maps and choose another option. This hurts your app’s reputation and may lead to negative reviews.

Step 4: Validate the GeoJSON File

A malformed GeoJSON file will be rejected by App Store Connect or cause unpredictable behavior in Apple Maps. Validate thoroughly before submitting.

Structural Validation

Run your file through a GeoJSON validator:

Online tools:

  • geojsonlint.com - validates structure and geometry
  • geojson.io - validates and visualizes simultaneously

Command-line tools:

# Using Python with geojson library
python -c "
import json
with open('coverage.geojson') as f:
    data = json.load(f)
print('Type:', data['type'])
for i, feature in enumerate(data['features']):
    geom = feature['geometry']
    print(f'Feature {i}: {geom[\"type\"]} with {len(geom[\"coordinates\"])} rings')
"

Using ogr2ogr (GDAL):

ogrinfo -al coverage.geojson

Common Validation Errors

ErrorCauseFix
Invalid JSONSyntax error (missing comma, bracket, quote)Run through a JSON linter first
Self-intersecting polygonPolygon edges cross each otherSimplify the polygon or fix the crossing points
Unclosed ringFirst and last coordinates don’t matchCopy the first coordinate to the end of the ring
Wrong coordinate orderLatitude-longitude instead of longitude-latitudeSwap all coordinate pairs
Clockwise exterior ringExterior rings should be counterclockwise per RFC 7946Reverse the coordinate order
Too many decimal placesCoordinates with 15 decimal places bloat the fileRound to 6 decimal places (11 cm precision)
Empty geometryFeature has no coordinatesRemove the empty feature or add coordinates

Visual Verification

Always render your coverage file on a map before submitting:

  1. Open geojson.io in a browser
  2. Paste your GeoJSON content or drag the file onto the map
  3. Verify the polygons cover the correct geographic areas
  4. Check for gaps between adjacent polygons (if covering multiple connected areas)
  5. Verify the polygons don’t extend into areas you can’t serve (oceans, countries without coverage)

Size Optimization

If your coverage file exceeds 5 MB:

  • Reduce coordinate precision to 5-6 decimal places
  • Simplify polygon boundaries (fewer points for the same shape)
  • Use tools like mapshaper.org to reduce vertex count while preserving shape
  • Remove unnecessary properties from feature objects

Apple accepts files up to 20 MB, but smaller files process faster and are less likely to encounter submission issues.

Step 5: Test Routing Integration Locally

Before submitting to App Store Connect, test the entire flow locally to confirm your app correctly receives and handles routing requests.

Simulator Configuration

Set a custom location in the iOS Simulator that falls within your coverage area:

  1. Run your app in the Simulator
  2. Go to Features > Location > Custom Location
  3. Enter coordinates inside your coverage polygon (e.g., 53.3498, -6.2603 for Dublin)

Testing with Maps

With your app installed on the Simulator:

  1. Open Apple Maps
  2. Search for a destination within your coverage area
  3. Tap “Directions”
  4. Your app should appear as a routing option if:
    • The route endpoints fall within your coverage polygon
    • Your app declares the correct routing mode for that area
    • Your Info.plist configuration is correct

If your app doesn’t appear, check:

  • The simulator location is within your coverage polygon
  • The routing mode you’re testing matches what your Info.plist declares
  • Your app’s Maps capability is correctly configured
  • The coverage file is included in your app bundle (for local testing)

Testing on a Physical Device

For more realistic testing, install your development build on a physical device:

  1. Open Maps while physically located in your coverage area (or use a GPX file to simulate the location)
  2. Request directions between two points in your coverage area
  3. Verify your app appears as an option
  4. Select your app and verify the routing request is received correctly

MKDirectionsRequest Debugging

Add logging to your routing request handler to verify the data you receive:

func application(_ app: UIApplication,
                 open url: URL,
                 options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {

    guard let request = MKDirectionsRequest(contentsOf: url) else {
        print("[Routing] Failed to parse MKDirectionsRequest from URL: \(url)")
        return false
    }

    if let source = request.source?.placemark.coordinate {
        print("[Routing] Source: \(source.latitude), \(source.longitude)")
    }

    if let dest = request.destination?.placemark.coordinate {
        print("[Routing] Destination: \(dest.latitude), \(dest.longitude)")
    }

    print("[Routing] Transport type: \(request.transportType.rawValue)")
    print("[Routing] Departure date: \(String(describing: request.departureDate))")
    print("[Routing] Arrival date: \(String(describing: request.arrivalDate))")

    return true
}

Step 6: Submit Through App Store Connect

Once testing confirms everything works, submit your coverage file alongside your app binary.

Upload Process

In App Store Connect:

  1. Navigate to your app > App Information
  2. Scroll to the “Routing App Coverage File” section
  3. Upload your .geojson file
  4. Verify the upload shows the correct file name and size

The coverage file is associated with your app, not a specific version. You can update it independently of app updates, though changes still go through Apple’s review process.

Submission Checklist

Before submitting, verify:

  • GeoJSON file passes structural validation
  • Polygons cover your actual service area (not more, not less)
  • Coordinate order is longitude-latitude
  • All polygon rings are closed
  • Properties include correct routing mode identifiers
  • File size is under 20 MB
  • Info.plist declares matching routing modes
  • Maps capability is enabled in your app target
  • URL scheme handles MKDirectionsRequest
  • Local testing confirms routing requests work
  • App correctly displays directions for received requests

Review Expectations

Apple reviews coverage files as part of the app review process. Common rejection reasons:

  • Coverage area doesn’t match app functionality: You claim to cover an area but your app can’t actually provide directions there
  • Incorrect routing modes: Your Info.plist declares transit mode but your app only provides driving directions
  • Malformed GeoJSON: The file has structural errors that slipped past your validation
  • Excessive coverage: Claiming global coverage when your app only works in specific cities

If rejected, Apple’s review notes will explain the issue. Fix the specific problem and resubmit.

Step 7: Handle Incoming Routing Requests

When a user selects your app in Apple Maps, iOS sends a routing request to your app. Your code needs to parse this request and display relevant directions.

Parsing the Request

Implement the URL handler in your AppDelegate or SceneDelegate:

func application(_ app: UIApplication,
                 open url: URL,
                 options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {

    guard let request = MKDirectionsRequest(contentsOf: url) else {
        return false
    }

    let routingHandler = RoutingRequestHandler()
    routingHandler.handleRequest(request)
    return true
}

Extracting Route Parameters

The MKDirectionsRequest object contains:

class RoutingRequestHandler {

    func handleRequest(_ request: MKDirectionsRequest) {
        // Source location (where the user is or wants to start)
        let source = request.source?.placemark.coordinate
        let sourceName = request.source?.placemark.name

        // Destination location
        let destination = request.destination?.placemark.coordinate
        let destinationName = request.destination?.placemark.name

        // Transport type
        let mode = request.transportType
        // .automobile, .transit, .walking, .cycling

        // Time preferences
        let departureDate = request.departureDate
        let arrivalDate = request.arrivalDate

        // Route the user to your directions screen
        showDirections(
            from: source,
            to: destination,
            mode: mode,
            departAt: departureDate,
            arriveBy: arrivalDate
        )
    }

    func showDirections(from source: CLLocationCoordinate2D?,
                        to destination: CLLocationCoordinate2D?,
                        mode: MKDirectionsTransportType,
                        departAt: Date?,
                        arriveBy: Date?) {

        // Navigate to your directions view controller
        // with the provided parameters
    }
}

Handling Edge Cases

Missing source location: The source may be nil if the user didn’t specify a starting point. Default to the user’s current location:

let startCoordinate = source ?? locationManager.location?.coordinate

Missing destination: Rare but possible. Show your app’s default search screen so the user can enter a destination.

Unsupported route: If the requested route falls partially outside your coverage (e.g., the source is in your area but the destination is not), handle gracefully:

if !coverageArea.contains(destination) {
    showErrorMessage("We don't have coverage for this destination yet. Please try another routing app.")
    return
}

App launched from background: If your app was in the background when the routing request came in, handle the request in both application(_:open:options:) and scene(_:openURLContexts:) to cover both launch and resume scenarios.

Deep Linking from Routing Requests

Track routing requests as a discovery channel in your analytics:

Analytics.log(event: "routing_request_received", properties: [
    "source": "apple_maps",
    "transport_type": mode.description,
    "has_departure_time": departureDate != nil,
    "has_arrival_time": arrivalDate != nil
])

This data helps you understand how much traffic Apple Maps drives to your app and whether those users convert to regular usage.

Step 8: Monitor and Expand Coverage

After launch, track how your routing integration performs and plan coverage expansion.

Performance Metrics

Track these metrics to understand the value of your Apple Maps integration:

  • Routing requests received: How many times per day/week Apple Maps sends users to your app
  • Request completion rate: How many routing requests result in the user actually following the directions
  • Session duration from routing: How long do users who come from Maps stay in your app
  • Install attribution: If possible, measure how many users discover your app because it appeared in Maps
  • Coverage utilization: Which parts of your coverage area generate the most routing requests

Expanding to New Areas

When your app adds coverage for new cities or regions:

  1. Update your GeoJSON file with new polygon features
  2. Test the new boundaries locally
  3. Upload the updated file to App Store Connect
  4. Submit for review (coverage file updates don’t require a new app version, but they do require review)

Plan coverage expansion based on:

  • User demand: Cities where users request your app most (check support tickets, app reviews, social media)
  • Transit data availability: Cities where you have reliable transit data (GTFS feeds, real-time API access)
  • Competitive landscape: Cities where no other routing app provides good coverage (use ASODOG to analyze which transit apps are popular in different regions)
  • Market size: Cities with high public transit ridership and smartphone penetration

Keeping Coverage Current

Transit networks change. Bus routes are added, train lines are extended, service areas grow. Update your coverage file whenever:

  • Your transit data adds new routes that extend beyond your current coverage polygon
  • You discontinue service in an area (shrink the polygon)
  • Administrative boundaries change (city mergers, new transit authority service areas)
  • You improve routing accuracy in areas that were previously too unreliable to include

Set a quarterly reminder to review your coverage boundaries against your actual routing data. A coverage file that is out of sync with your app’s capabilities leads to either missed users (coverage too small) or frustrated users (coverage too large).

ASO Impact of Routing Integration

Your Apple Maps routing integration affects your overall App Store Optimization in several ways:

  • Discovery channel: Users who find your app through Maps are high-intent organic users, contributing to your download velocity
  • Engagement signals: Routing sessions count as active app usage, improving your engagement metrics
  • Retention boost: Users who use your app for daily commute routing tend to have higher retention than users from other channels
  • Keyword relevance: Routing activity reinforces your app’s relevance for transit and navigation keywords in App Store search
  • Review quality: Users who find genuine utility in your routing tend to leave detailed positive reviews

Use ASODOG to correlate coverage expansion milestones with changes in your keyword rankings, category position, and download trends. This data helps you quantify the ASO value of investing in broader geographic coverage.