def read_protofeed(path):
    """
    Read the data files at the given directory path
    (string or Path object) and build a ProtoFeed from them.
    Validate the resulting ProtoFeed.
    If invalid, raise a ``ValueError`` specifying the errors.
    Otherwise, return the resulting ProtoFeed.

    The data files needed to build a ProtoFeed are

    - ``frequencies.csv``: (required) A CSV file containing route frequency
      information. The CSV file contains the columns

      - ``route_short_name``: (required) String. A unique short name
        for the route, e.g. '51X'
      - ``route_long_name``: (required) String. Full name of the route
        that is more descriptive than ``route_short_name``
      - ``route_type``: (required) Integer. The
        `GTFS type of the route <https://developers.google.com/transit/gtfs/reference/#routestxt>`_
      - ``service_window_id`` (required): String. A service window ID
        for the route taken from the file ``service_windows.csv``
      - ``direction``: (required) Integer 0, 1, or 2. Indicates
        whether the route travels in GTFS direction 0, GTFS direction
        1, or in both directions.
        In the latter case, trips will be created that travel in both
        directions along the route's path, each direction operating at
        the given frequency.  Otherwise, trips will be created that
        travel in only the given direction.
      - ``frequency`` (required): Integer. The frequency of the route
        during the service window in vehicles per hour.
      - ``speed``:  (optional) Float. The speed of the route in
        kilometers per hour
      - ``shape_id``: (required) String. A shape ID that is listed in
        ``shapes.geojson`` and corresponds to the linestring of the
        (route, direction, service window) tuple.

    - ``meta.csv``: (required) A CSV file containing network metadata.
      The CSV file contains the columns

      - ``agency_name``: (required) String. The name of the transport
        agency
      - ``agency_url``: (required) String. A fully qualified URL for
        the transport agency
      - ``agency_timezone``: (required) String. Timezone where the
        transit agency is located. Timezone names never contain the
        space character but may contain an underscore. Refer to
        `http://en.wikipedia.org/wiki/List_of_tz_zones <http://en.wikipedia.org/wiki/List_of_tz_zones>`_ for a list of valid values
      - ``start_date``, ``end_date`` (required): Strings. The start
        and end dates for which all this network information is valid
        formated as YYYYMMDD strings
      - ``default_route_speed``: (required) Float. Default speed in
        kilometers per hour to assign to routes with no ``speed``
        entry in the file ``routes.csv``

    - ``service_windows.csv``: (required) A CSV file containing service window
      information.
      A *service window* is a time interval and a set of days of the
      week during which all routes have constant service frequency,
      e.g. Saturday and Sunday 07:00 to 09:00.
      The CSV file contains the columns

      - ``service_window_id``: (required) String. A unique identifier
        for a service window
      - ``start_time``, ``end_time``: (required) Strings. The start
        and end times of the service window in HH:MM:SS format where
        the hour is less than 24
      - ``monday``, ``tuesday``, ``wednesday``, ``thursday``,
        ``friday``, ``saturday``, ``sunday`` (required): Integer 0
        or 1. Indicates whether the service is active on the given day
        (1) or not (0)

    - ``shapes.geojson``: (required) A GeoJSON file containing route shapes.
      The file consists of one feature collection of LineString
      features, where each feature's properties contains at least the
      attribute ``shape_id``, which links the route's shape to the
      route's information in ``routes.csv``.

    - ``stops.csv``: (optional) A CSV file containing all the required
      and optional fields of ``stops.txt`` in
      `the GTFS <https://developers.google.com/transit/gtfs/reference/#stopstxt>`_

    """
    path = Path(path)

    service_windows = pd.read_csv(
      path/'service_windows.csv')

    meta = pd.read_csv(path/'meta.csv',
      dtype={'start_date': str, 'end_date': str})

    shapes = gpd.read_file(str(path/'shapes.geojson'), driver='GeoJSON')

    if (path/'stops.csv').exists():
        stops = (
            pd.read_csv(path/'stops.csv', dtype={
                'stop_id': str,
                'stop_code': str,
                'zone_id': str,
                'location_type': int,
                'parent_station': str,
                'stop_timezone': str,
                'wheelchair_boarding': int,
            })
            .drop_duplicates(subset=['stop_lon', 'stop_lat'])
            .dropna(subset=['stop_lon', 'stop_lat'], how='any')
        )
    else:
        stops = None

    frequencies = pd.read_csv(path/'frequencies.csv', dtype={
        'route_short_name': str,
        'service_window_id': str,
        'shape_id': str,
        'direction': int,
        'frequency': int,
    })

    pfeed = ProtoFeed(frequencies, meta, service_windows, shapes, stops)

    # Validate
    v = vd.validate(pfeed)
    if 'error' in v.type.values:
        raise ValueError(
          "Invalid ProtoFeed files:\n\n" + v.to_string(justify='left'))

    return pfeed