示例#1
0
    def handle_archive(self, archive_name):
        self.route = None
        self.trip = None
        self.routes = {}
        self.calendars = {}
        self.stop_times = []
        self.notes = []
        if 'ulb' in archive_name.lower():
            source_name = 'ULB'
        else:
            source_name = 'MET'
        self.source, source_created = DataSource.objects.get_or_create(name=source_name)
        self.source.datetime = timezone.localtime()

        with zipfile.ZipFile(archive_name) as archive:
            for filename in archive.namelist():
                if filename.endswith('.cif'):
                    with archive.open(filename) as open_file:
                        self.handle_file(open_file)
        assert self.stop_times == []

        for route in self.routes.values():
            groupings = get_stop_usages(route.trip_set.all())

            route.service.stops.clear()
            stop_usages = [
                StopUsage(service=route.service, stop_id=stop_time.stop_id, timing_status=stop_time.timing_status,
                          direction='outbound', order=i)
                for i, stop_time in enumerate(groupings[0])
            ] + [
                StopUsage(service=route.service, stop_id=stop_time.stop_id, timing_status=stop_time.timing_status,
                          direction='inbound', order=i)
                for i, stop_time in enumerate(groupings[1])
            ]
            StopUsage.objects.bulk_create(stop_usages)

            # self.stops doesn't contain all stops, and has latlongs in the Irish Grid projection
            stops = StopPoint.objects.in_bulk(stop_usage.stop_id for stop_usage in stop_usages)
            line_strings = []
            for pattern in get_journey_patterns(route.trip_set.all()):
                points = (stops[stop_code].latlong for stop_code in pattern if stop_code in stops)
                line_strings.append(LineString(*points))
            route.service.geometry = MultiLineString(*line_strings)

        services = {route.service.id: route.service for route in self.routes.values()}.values()
        Service.objects.bulk_update(services,
                                    fields=['geometry', 'description', 'outbound_description', 'inbound_description'])
        for service in services:
            service.update_search_vector()

        self.source.route_set.exclude(code__in=self.routes.keys()).delete()
        self.source.service_set.filter(current=True).exclude(service_code__in=self.routes.keys()).update(current=False)
        self.source.save(update_fields=['datetime'])
示例#2
0
 def handle_stop(cls, line):
     record_identity = line[:2]
     atco_code = line[2:14]
     if atco_code not in cls.services[cls.service_code][cls.direction]:
         if not StopPoint.objects.filter(atco_code=atco_code).exists():
             if not atco_code.startswith('7'):
                 print(atco_code)
                 return
             cls.deferred_stop_codes.append(atco_code)
         if record_identity == 'QI':
             timing_status = line[26:28]
             order = 1
         else:
             timing_status = line[21:23]
             if record_identity == 'QO':
                 order = 0
             else:
                 order = 2
         stop_usage = StopUsage(
             service_id=cls.service_code,
             stop_id=atco_code,
             direction=('Outbound' if cls.direction == 'O' else 'Inbound'),
             timing_status=('PTP' if timing_status == 'T1' else 'OTH'),
             order=order)
         cls.services[cls.service_code][
             cls.direction][atco_code] = stop_usage
         cls.stop_usages.append(stop_usage)
    def handle_service(self, filename, transxchange, txc_service, today,
                       stops):
        if txc_service.operating_period.end:
            if txc_service.operating_period.end < today:
                print(filename, txc_service.operating_period.end)
                return
            elif txc_service.operating_period.end < txc_service.operating_period.start:
                return

        operators = self.get_operators(transxchange, txc_service)

        if not operators:
            basename = os.path.basename(filename)  # e.g. 'KCTB_'
            if basename[4] == '_':
                maybe_operator_code = basename[:4]
                if maybe_operator_code.isupper(
                ) and maybe_operator_code.isalpha():
                    try:
                        operators = [
                            Operator.objects.get(id=maybe_operator_code)
                        ]
                    except Operator.DoesNotExist:
                        pass

        if self.is_tnds() and self.source.name != 'L':
            if operators and all(operator.id in self.open_data_operators
                                 for operator in operators):
                return

        linked_services = []

        description = self.get_description(txc_service)

        if description == 'Origin - Destination':
            description = ''

        if re.match(r'^P[BCDFGHKM]\d+:\d+.*.$', txc_service.service_code):
            unique_service_code = txc_service.service_code
        else:
            unique_service_code = None

        for line in txc_service.lines:
            existing = None
            service_code = None

            if unique_service_code:
                # first try getting by BODS profile compliant service code
                existing = Service.objects.filter(
                    service_code=unique_service_code,
                    line_name__iexact=line.line_name).order_by(
                        '-current', 'id').first()

            if not existing and operators and line.line_name:
                if self.source.name in {'Go South West', 'Oxford Bus Company'}:
                    assert operators[0].parent
                    existing = Service.objects.filter(
                        operator__parent=operators[0].parent)

                    if self.source.name == 'Oxford Bus Company':
                        if txc_service.service_code.startswith('T'):
                            operators = Operator.objects.filter(id='THTR')
                        elif txc_service.service_code.startswith('C'):
                            operators = Operator.objects.filter(id='CSLB')
                    elif self.source.name == 'Go South West':
                        if txc_service.service_code.startswith('GC'):
                            operators = Operator.objects.filter(id='TFCN')

                elif all(operator.parent == 'Go South Coast'
                         for operator in operators):
                    existing = Service.objects.filter(
                        operator__parent='Go South Coast')
                elif self.source.name.startswith('Stagecoach'):
                    existing = Service.objects.filter(
                        Q(source=self.source) | Q(operator__in=operators))
                    if description:
                        existing = existing.filter(description=description)
                else:
                    existing = Service.objects.filter(operator__in=operators)

                if len(transxchange.services) == 1:
                    has_stop_time = Exists(
                        StopTime.objects.filter(
                            stop__in=stops,
                            trip__route__service=OuterRef('id')))
                    has_stop_usage = Exists(
                        StopUsage.objects.filter(stop__in=stops,
                                                 service=OuterRef('id')))
                    has_no_route = ~Exists(
                        Route.objects.filter(service=OuterRef('id')))
                    existing = existing.filter(has_stop_time
                                               | (has_stop_usage
                                                  & has_no_route))
                elif len(txc_service.lines) == 1:
                    existing = existing.filter(
                        Exists(
                            Route.objects.filter(
                                service_code=txc_service.service_code,
                                service=OuterRef('id'))))
                elif description:
                    existing = existing.filter(description=description)

                existing = existing.filter(
                    line_name__iexact=line.line_name).order_by(
                        '-current', 'id').first()

            if self.is_tnds():
                if self.should_defer_to_other_source(operators,
                                                     line.line_name):
                    continue

                service_code = get_service_code(filename)
                if service_code is None:
                    service_code = txc_service.service_code

                if not existing:
                    # assume service code is at least unique within a TNDS region
                    existing = self.source.service_set.filter(
                        service_code=service_code).first()
            elif unique_service_code:
                service_code = unique_service_code

            if existing:
                service = existing
            else:
                service = Service()

            service.line_name = line.line_name
            service.date = today
            service.current = True
            service.source = self.source
            service.show_timetable = True

            if service_code:
                service.service_code = service_code

            if description:
                service.description = description

            line_brand = line.line_brand
            if txc_service.marketing_name and txc_service.marketing_name != 'CornwallbyKernow':
                line_brand = txc_service.marketing_name
                if line.line_name in line_brand:
                    line_brand_parts = line_brand.split()
                    if line.line_name in line_brand_parts:
                        line_brand_parts.remove(line.line_name)
                        line_brand = ' '.join(line_brand_parts)
                print(line_brand)
            if line_brand:
                service.line_brand = line_brand

            if txc_service.mode:
                service.mode = txc_service.mode

            if self.region_id:
                service.region_id = self.region_id

            if self.service_descriptions:  # NCSD
                service.outbound_description, service.inbound_description = self.get_service_descriptions(
                    filename)
                service.description = service.outbound_description or service.inbound_description

            if service.id:
                service_created = False
            else:
                service_created = True
            service.save()

            if not service_created:
                if '_' in service.slug or '-' not in service.slug or existing and not existing.current:
                    service.slug = ''
                    service.save(update_fields=['slug'])

            if operators:
                if service_created:
                    service.operator.set(operators)
                else:
                    if self.source.name in {
                            'Oxford Bus Company', 'Go South West'
                    }:
                        pass
                    elif service.id in self.service_ids or all(
                            o.parent == 'Go South Coast' for o in operators):
                        service.operator.add(*operators)
                    else:
                        service.operator.set(operators)
            self.service_ids.add(service.id)
            linked_services.append(service.id)

            journeys = transxchange.get_journeys(txc_service.service_code,
                                                 line.id)

            if journeys:
                journey = journeys[0]

                ticket_machine_service_code = journey.ticket_machine_service_code
                if ticket_machine_service_code and ticket_machine_service_code != line.line_name:
                    try:
                        ServiceCode.objects.create(
                            scheme='SIRI',
                            code=ticket_machine_service_code,
                            service=service)
                    except IntegrityError:
                        pass

                # a code used in Traveline Cymru URLs:
                if self.source.name == 'W':
                    private_code = journey.private_code
                    if private_code and ':' in private_code:
                        ServiceCode.objects.update_or_create(
                            {'code': private_code.split(':', 1)[0]},
                            service=service,
                            scheme='Traveline Cymru')

            # timetable data:

            route_defaults = {
                'line_name': line.line_name,
                'line_brand': line_brand,
                'start_date': txc_service.operating_period.start,
                'end_date': txc_service.operating_period.end,
                'dates': txc_service.operating_period.dates(),
                'service': service,
                'revision_number': transxchange.attributes['RevisionNumber'],
                'service_code': txc_service.service_code
            }
            if description:
                route_defaults['description'] = description

            geometry = []
            if transxchange.route_sections:
                patterns = {
                    journey.journey_pattern.id: journey.journey_pattern
                    for journey in journeys
                }
                routes = [
                    pattern.route_ref for pattern in patterns.values()
                    if pattern.route_ref
                ]
                if routes:
                    routes = [
                        transxchange.routes[route_id]
                        for route_id in transxchange.routes
                        if route_id in routes
                    ]
                    for route in routes:
                        for section_ref in route.route_section_refs:
                            section = transxchange.route_sections[section_ref]
                            for link in section.links:
                                if link.track:
                                    geometry.append(link.track)
                else:
                    route_links = {}
                    for section in transxchange.route_sections.values():
                        for link in section.links:
                            route_links[link.id] = link
                    for journey in journeys:
                        if journey.journey_pattern:
                            for section in journey.journey_pattern.sections:
                                for link in section.timinglinks:
                                    link = route_links[link.route_link_ref]
                                    if link.track:
                                        geometry.append(link.track)
                if geometry:
                    geometry = MultiLineString(geometry).simplify()
                    if not isinstance(geometry, MultiLineString):
                        geometry = MultiLineString(geometry)
                    route_defaults['geometry'] = geometry

            route_code = filename
            if len(transxchange.services) > 1:
                route_code += f'#{txc_service.service_code}'
            if len(txc_service.lines) > 1:
                route_code += f'#{line.id}'

            route, route_created = Route.objects.update_or_create(
                route_defaults, source=self.source, code=route_code)
            self.route_ids.add(route.id)
            if not route_created:
                # if 'opendata.ticketer' in self.source.url and route.service_id == service_id:
                #     continue
                route.trip_set.all().delete()

            self.handle_journeys(route, stops, journeys, txc_service, line.id)

            service.stops.clear()
            outbound, inbound = get_stop_usages(
                Trip.objects.filter(route__service=service))

            changed_fields = []

            if self.source.name.startswith(
                    'Arriva ') or self.source.name == 'Yorkshire Tiger':
                if outbound:
                    changed = 0
                    origin_stop = outbound[0].stop
                    destination_stop = outbound[-1].stop
                    if txc_service.origin in origin_stop.common_name:
                        if origin_stop.locality.name not in txc_service.origin:
                            txc_service.origin = f'{origin_stop.locality.name} {txc_service.origin}'
                        changed += 1
                    if txc_service.destination in destination_stop.common_name:
                        if destination_stop.locality.name not in txc_service.destination:
                            txc_service.destination = f'{destination_stop.locality.name} {txc_service.destination}'
                        changed += 1
                    if changed == 2:
                        service.description = f'{txc_service.origin} - {txc_service.destination}'
                        changed_fields.append('description')

            stop_usages = [
                StopUsage(service=service,
                          stop_id=stop_time.stop_id,
                          timing_status=stop_time.timing_status,
                          direction='outbound',
                          order=i) for i, stop_time in enumerate(outbound)
            ] + [
                StopUsage(service=service,
                          stop_id=stop_time.stop_id,
                          timing_status=stop_time.timing_status,
                          direction='inbound',
                          order=i) for i, stop_time in enumerate(inbound)
            ]
            StopUsage.objects.bulk_create(stop_usages)

            if outbound:
                outbound = Grouping(txc_service, outbound[0].stop,
                                    outbound[-1].stop)
                outbound_description = str(outbound)
                if outbound_description != service.outbound_description:
                    service.outbound_description = outbound_description
                    changed_fields.append('outbound_description')
            if inbound:
                inbound = Grouping(txc_service, inbound[0].stop,
                                   inbound[-1].stop)
                inbound_description = str(inbound)
                if inbound_description != service.inbound_description:
                    service.inbound_description = inbound_description
                    changed_fields.append('inbound_description')
            if changed_fields:
                service.save(update_fields=changed_fields)

            service_code = service.service_code
            if service_code in self.corrections:
                corrections = {}
                for field in self.corrections[service_code]:
                    if field == 'operator':
                        service.operator.set(
                            self.corrections[service_code][field])
                    else:
                        corrections[field] = self.corrections[service_code][
                            field]
                Service.objects.filter(service_code=service_code).update(
                    **corrections)

            service.update_search_vector()

        if len(linked_services) > 1:
            for i, from_service in enumerate(linked_services):
                for i, to_service in enumerate(linked_services[i + 1:]):
                    kwargs = {
                        'from_service_id': from_service,
                        'to_service_id': to_service,
                    }
                    if not ServiceLink.objects.filter(**kwargs).exists():
                        ServiceLink.objects.create(**kwargs, how='also')
示例#4
0
def handle_zipfile(path, collection, url):
    source = DataSource.objects.update_or_create(
        {
            'url': url,
            'datetime': timezone.now()
        }, name=f'{collection} GTFS')[0]

    shapes = {}
    service_shapes = {}
    operators = {}
    routes = {}
    services = set()
    headsigns = {}

    with zipfile.ZipFile(path) as archive:

        for line in read_file(archive, 'shapes.txt'):
            shape_id = line['shape_id']
            if shape_id not in shapes:
                shapes[shape_id] = []
            shapes[shape_id].append(
                Point(float(line['shape_pt_lon']),
                      float(line['shape_pt_lat'])))

        for line in read_file(archive, 'agency.txt'):
            operator, created = Operator.objects.get_or_create(
                {
                    'name': line['agency_name'],
                    'region_id': 'LE'
                },
                id=line['agency_id'],
                region__in=['CO', 'UL', 'MU', 'LE', 'NI'])
            if not created and operator.name != line['agency_name']:
                print(operator, line)
            operators[line['agency_id']] = operator

        for line in read_file(archive, 'routes.txt'):
            if line['route_short_name'] and len(line['route_short_name']) <= 8:
                route_id = line['route_short_name']
            elif line['route_long_name'] and len(line['route_long_name']) <= 4:
                route_id = line['route_long_name']
            else:
                route_id = line['route_id'].split()[0]

            service_code = collection + '-' + route_id
            assert len(service_code) <= 24

            defaults = {
                'region_id': 'LE',
                'line_name': line['route_short_name'],
                'description': line['route_long_name'],
                'date': time.strftime('%Y-%m-%d'),
                'mode': MODES.get(int(line['route_type']), ''),
                'current': True,
                'show_timetable': True
            }
            service, created = Service.objects.update_or_create(
                defaults, service_code=service_code, source=source)

            try:
                operator = operators[line['agency_id']]
                if service in services:
                    service.operator.add(operator)
                else:
                    service.operator.set([operator])
            except KeyError:
                pass
            services.add(service)

            route, created = Route.objects.update_or_create(
                {
                    'line_name': line['route_short_name'],
                    'description': line['route_long_name'],
                    'service': service,
                },
                source=source,
                code=line['route_id'],
            )
            if not created:
                route.trip_set.all().delete()
            routes[line['route_id']] = route

        stops, stops_not_created = do_stops(archive)

        calendars = {}
        for line in read_file(archive, 'calendar.txt'):
            calendar = Calendar(
                mon='1' == line['monday'],
                tue='1' == line['tuesday'],
                wed='1' == line['wednesday'],
                thu='1' == line['thursday'],
                fri='1' == line['friday'],
                sat='1' == line['saturday'],
                sun='1' == line['sunday'],
                start_date=parse_date(line['start_date']),
                end_date=parse_date(line['end_date']),
            )
            calendar.save()
            calendars[line['service_id']] = calendar

        for line in read_file(archive, 'calendar_dates.txt'):
            CalendarDate.objects.create(
                calendar=calendars[line['service_id']],
                start_date=parse_date(line['date']),
                end_date=parse_date(line['date']),
                operation=line['exception_type'] == '1')

        trips = {}
        for line in read_file(archive, 'trips.txt'):
            route = routes[line['route_id']]
            trips[line['trip_id']] = Trip(
                route=route,
                calendar=calendars[line['service_id']],
                inbound=line['direction_id'] == '1')
            if route.service_id not in service_shapes:
                service_shapes[route.service_id] = set()
            service_shapes[route.service_id].add(line['shape_id'])
            if line['trip_headsign']:
                if line['route_id'] not in headsigns:
                    headsigns[line['route_id']] = {
                        '0': set(),
                        '1': set(),
                    }
                headsigns[line['route_id']][line['direction_id']].add(
                    line['trip_headsign'])
        for route_id in headsigns:
            route = routes[route_id]
            if not route.service.description:
                origins = headsigns[route_id]['1']
                destinations = headsigns[route_id]['0']
                origin = None
                destination = None
                if len(origins) <= 1 and len(destinations) <= 1:
                    if len(origins) == 1:
                        origin = list(origins)[0]
                    if len(destinations) == 1:
                        destination = list(destinations)[0]

                    if origin and ' - ' in origin:
                        route.service.inbound_description = origin
                        route.service.description = origin
                    if destination and ' - ' in destination:
                        route.service.outbound_description = destination
                        route.service.description = destination

                    if origin and destination and ' - ' not in origin:
                        route.service.description = route.service.outbound_description = f'{origin} - {destination}'
                        route.service.inbound_description = f'{destination} - {origin}'

                    route.service.save(update_fields=[
                        'description', 'inbound_description',
                        'outbound_description'
                    ])

        stop_times = []
        trip_id = None
        trip = None
        for line in read_file(archive, 'stop_times.txt'):
            if trip_id != line['trip_id']:
                if trip:
                    trip.start = stop_times[0].departure
                    trip.end = stop_times[-1].arrival
                    trip.save()
                    for stop_time in stop_times:
                        stop_time.trip = trip
                    StopTime.objects.bulk_create(stop_times)
                    stop_times = []
                trip = Trip()
            trip_id = line['trip_id']
            trip = trips[trip_id]
            stop = stops.get(line['stop_id'])
            stop_time = StopTime(
                stop=stop,
                arrival=line['arrival_time'],
                departure=line['departure_time'],
                sequence=line['stop_sequence'],
            )
            if stop:
                trip.destination = stop
            elif line['stop_id'] in stops_not_created:
                stop_time.stop_code = stops_not_created[line['stop_id']]
            else:
                stop_time.stop_code = line['stop_id']
                print(line)
            stop_times.append(stop_time)
    trip.start = stop_times[0].departure
    trip.end = stop_times[-1].arrival
    trip.save()
    for stop_time in stop_times:
        stop_time.trip = trip
    StopTime.objects.bulk_create(stop_times)

    for service in services:
        if service.id in service_shapes:
            linestrings = [
                LineString(*shapes[shape])
                for shape in service_shapes[service.id] if shape in shapes
            ]
            service.geometry = MultiLineString(*linestrings)
            service.save(update_fields=['geometry'])

        groupings = get_stop_usages(
            Trip.objects.filter(route__service=service))

        service.stops.clear()
        stop_usages = [
            StopUsage(service=service,
                      stop_id=stop_time.stop_id,
                      timing_status=stop_time.timing_status,
                      direction='outbound',
                      order=i) for i, stop_time in enumerate(groupings[0])
        ] + [
            StopUsage(service=service,
                      stop_id=stop_time.stop_id,
                      timing_status=stop_time.timing_status,
                      direction='inbound',
                      order=i) for i, stop_time in enumerate(groupings[1])
        ]
        StopUsage.objects.bulk_create(stop_usages)

        service.region = Region.objects.filter(
            adminarea__stoppoint__service=service).annotate(
                Count('adminarea__stoppoint__service')).order_by(
                    '-adminarea__stoppoint__service__count').first()
        if service.region:
            service.save(update_fields=['region'])
        service.update_search_vector()

    for operator in operators.values():
        operator.region = Region.objects.filter(
            adminarea__stoppoint__service__operator=operator).annotate(
                Count('adminarea__stoppoint__service__operator')).order_by(
                    '-adminarea__stoppoint__service__operator__count').first()
        if operator.region_id:
            operator.save(update_fields=['region'])

    print(
        source.service_set.filter(current=True).exclude(
            route__in=routes.values()).update(current=False))
    print(
        source.service_set.filter(current=True).exclude(
            route__trip__isnull=False).update(current=False))
    print(
        source.route_set.exclude(
            id__in=(route.id for route in routes.values())).delete())
    StopPoint.objects.filter(active=False,
                             service__current=True).update(active=True)
    StopPoint.objects.filter(active=True,
                             service__isnull=True).update(active=False)
示例#5
0
    def handle_service(self, filename, parts, transxchange, txc_service, today,
                       stops):
        if txc_service.operating_period.end:
            if txc_service.operating_period.end < today:
                return
            if txc_service.operating_period.end < txc_service.operating_period.start:
                return

        operators = self.get_operators(transxchange, txc_service)

        if self.is_tnds() and operators and all(
                operator.id in self.open_data_operators
                for operator in operators):
            return

        lines = get_lines(txc_service.element)

        linked_services = []

        description = self.get_description(txc_service)

        for line_id, line_name, line_brand in lines:
            existing = None

            if operators and description and line_name:
                if all(operator.parent == 'Go South Coast'
                       for operator in operators):
                    existing = Service.objects.filter(
                        operator__parent='Go South Coast')
                else:
                    existing = Service.objects.filter(operator__in=operators)
                existing = existing.filter(description=description,
                                           line_name=line_name)
                existing = existing.order_by('-current',
                                             'service_code').first()

            if self.is_tnds():
                if operators and all(operator.id in self.incomplete_operators
                                     for operator in operators):
                    if Service.objects.filter(
                            operator__in=operators,
                            line_name=line_name,
                            current=True).exclude(source=self.source).exists():
                        continue

                service_code = get_service_code(filename)
                if service_code is None:
                    service_code = txc_service.service_code

            else:
                if self.source.name in {'Go East Anglia', 'Go South West'
                                        } and not existing:
                    try:
                        existing = Service.objects.get(
                            operator__parent=self.source.name,
                            current=True,
                            line_name__iexact=line_name)
                    except (Service.DoesNotExist,
                            Service.MultipleObjectsReturned):
                        pass

                operator_code = '-'.join(operator.id for operator in operators)
                if operator_code == 'TDTR' and 'Swindon-Rural' in filename:
                    operator_code = 'SBCR'

                if parts:
                    service_code = f'{self.source.id}-{parts}-{line_name}'
                    if not existing:
                        existing = Service.objects.filter(
                            service_code=service_code,
                            line_name=line_name).first()
                    if not existing:
                        existing = self.source.service_set.filter(
                            line_name=line_name,
                            route__code__contains=f'/{parts}_').order_by(
                                '-current', 'service_code').first()
                else:
                    service_code = f'{self.source.id}-{operator_code}-{txc_service.service_code}'
                    if len(lines) > 1:
                        service_code += '-' + line_name

                    if not existing and operator_code != 'SBCR':
                        existing = self.get_existing_service(
                            line_name, operators)

            if existing and existing.id in self.service_ids and description and existing.description != description:
                existing = None

            if not existing:
                existing = Service.objects.filter(
                    service_code=service_code).first()
                if existing and existing.current and existing.line_name != line_name:
                    service_code = f'{service_code}-{line_name}'
                existing = None

            if existing:
                services = Service.objects.filter(id=existing.id)
            else:
                services = Service.objects.filter(service_code=service_code)

            defaults = {
                'line_name': line_name,
                'date': today,
                'current': True,
                'source': self.source,
                'show_timetable': True
            }
            if not existing:
                defaults['service_code'] = service_code

            if description:
                defaults['description'] = description

            if txc_service.mode:
                defaults['mode'] = txc_service.mode
            if line_brand:
                defaults['line_brand'] = line_brand
            if self.region_id:
                defaults['region_id'] = self.region_id

            if self.service_descriptions:  # NCSD
                defaults['outbound_description'], defaults[
                    'inbound_description'] = self.get_service_descriptions(
                        filename)
                defaults['description'] = defaults[
                    'outbound_description'] or defaults['inbound_description']

            try:
                service, service_created = services.update_or_create(defaults)
            except IntegrityError as e:
                print(e, service_code)
                continue

            if service_created:
                service.operator.set(operators)
            else:
                if '_' in service.slug or '-' not in service.slug or existing and not existing.current:
                    service.slug = ''
                    service.save(update_fields=['slug'])
                if self.source.name in {'Go East Anglia', 'Go South West'}:
                    pass
                elif service.id in self.service_ids or all(
                        o.parent == 'Go South Coast' for o in operators):
                    service.operator.add(*operators)
                else:
                    service.operator.set(operators)
            self.service_ids.add(service.id)
            linked_services.append(service.id)

            journeys = transxchange.get_journeys(txc_service.service_code,
                                                 line_id)

            # a code used in Traveline Cymru URLs:
            if self.source.name == 'W':
                if transxchange.journeys and journeys[0].private_code:
                    private_code = journeys[0].private_code
                    if ':' in private_code:
                        ServiceCode.objects.update_or_create(
                            {'code': private_code.split(':', 1)[0]},
                            service=service,
                            scheme='Traveline Cymru')

            # timetable data:

            route_defaults = {
                'line_name': line_name,
                'line_brand': line_brand,
                'start_date': txc_service.operating_period.start,
                'end_date': txc_service.operating_period.end,
                'dates': txc_service.operating_period.dates(),
                'service': service,
            }
            if 'description' in defaults:
                route_defaults['description'] = defaults['description']

            route_code = filename
            if len(transxchange.services) > 1:
                route_code += f'#{txc_service.service_code}'
            if len(lines) > 1:
                route_code += f'#{line_id}'

            route, route_created = Route.objects.update_or_create(
                route_defaults, source=self.source, code=route_code)
            if not route_created:
                route.trip_set.all().delete()
            self.route_ids.add(route.id)

            self.handle_journeys(route, stops, journeys, txc_service, line_id)

            service.stops.clear()
            outbound, inbound = get_stop_usages(
                Trip.objects.filter(route__service=service))

            changed_fields = []

            if self.source.name.startswith(
                    'Arriva ') or self.source.name == 'Yorkshire Tiger':
                if outbound:
                    changed = 0
                    origin_stop = outbound[0].stop
                    destination_stop = outbound[-1].stop
                    if origin_stop.common_name == txc_service.origin:
                        if origin_stop.locality.name not in txc_service.origin:
                            txc_service.origin = f'{origin_stop.locality.name} {txc_service.origin}'
                        changed += 1
                    if destination_stop.common_name == txc_service.destination:
                        if destination_stop.locality.name not in txc_service.destination:
                            txc_service.destination = f'{destination_stop.locality.name} {txc_service.destination}'
                        changed += 1
                    if changed == 2:
                        service.description = f'{txc_service.origin} - {txc_service.destination}'
                        changed_fields.append('description')

            stop_usages = [
                StopUsage(service=service,
                          stop_id=stop_time.stop_id,
                          timing_status=stop_time.timing_status,
                          direction='outbound',
                          order=i) for i, stop_time in enumerate(outbound)
            ] + [
                StopUsage(service=service,
                          stop_id=stop_time.stop_id,
                          timing_status=stop_time.timing_status,
                          direction='inbound',
                          order=i) for i, stop_time in enumerate(inbound)
            ]
            StopUsage.objects.bulk_create(stop_usages)

            if outbound:
                outbound = Grouping(txc_service, outbound[0].stop,
                                    outbound[-1].stop)
                outbound_description = str(outbound)
                if outbound_description != service.outbound_description:
                    service.outbound_description = outbound_description
                    changed_fields.append('outbound_description')
            if inbound:
                inbound = Grouping(txc_service, inbound[0].stop,
                                   inbound[-1].stop)
                inbound_description = str(inbound)
                if inbound_description != service.inbound_description:
                    service.inbound_description = inbound_description
                    changed_fields.append('inbound_description')
            if changed_fields:
                service.save(update_fields=changed_fields)

            service_code = service.service_code
            if service_code in self.corrections:
                corrections = {}
                for field in self.corrections[service_code]:
                    if field == 'operator':
                        service.operator.set(
                            self.corrections[service_code][field])
                    else:
                        corrections[field] = self.corrections[service_code][
                            field]
                Service.objects.filter(service_code=service_code).update(
                    **corrections)

            if service_code == 'twm_5-501-A-y11':  # Lakeside Coaches
                Trip.objects.filter(route__service=service,
                                    start='15:05').delete()
                Trip.objects.filter(route__service=service,
                                    start='15:30').delete()

            service.update_search_vector()

        if len(linked_services) > 1:
            for i, from_service in enumerate(linked_services):
                for i, to_service in enumerate(linked_services[i + 1:]):
                    kwargs = {
                        'from_service_id': from_service,
                        'to_service_id': to_service,
                    }
                    if not ServiceLink.objects.filter(**kwargs).exists():
                        ServiceLink.objects.create(**kwargs, how='also')
    def handle_file(self, open_file, filename):
        transxchange = TransXChange(open_file)

        try:
            service_element = transxchange.element.find('txc:Services/txc:Service', NS)
        except AttributeError:
            return

        if transxchange.mode == 'underground':
            return

        today = self.source.datetime.date()

        if transxchange.operating_period.end and transxchange.operating_period.end < today:
            return

        line_name, line_brand = get_line_name_and_brand(service_element)

        net, service_code, line_ver = infer_from_filename(filename)
        if service_code is None:
            service_code = transxchange.service_code

        if service_code not in self.service_codes:
            self.service_codes.add(service_code)
            self.source.route_set.filter(service=service_code).delete()

        defaults = {
            'line_name': line_name,
            'line_brand': line_brand,
            'mode': transxchange.mode,
            'net': net,
            'line_ver': line_ver,
            'region_id': self.region_id,
            'date': today,
            'current': True,
            'source': self.source,
            'show_timetable': True
        }
        description = transxchange.description
        if description:
            if self.region_id == 'NE':
                description = sanitize_description(description)
            defaults['description'] = description

        # stops:
        stops = StopPoint.objects.in_bulk(transxchange.stops.keys())

        groupings = {
            'outbound': Grouping('outbound', transxchange),
            'inbound': Grouping('inbound', transxchange)
        }

        for journey_pattern in transxchange.journey_patterns.values():
            if journey_pattern.direction == 'inbound':
                grouping = groupings['inbound']
            else:
                grouping = groupings['outbound']
            grouping.add_journey_pattern(journey_pattern)

        try:
            stop_usages = []
            for grouping in groupings.values():
                if grouping.rows:
                    stop_usages += [
                        StopUsage(
                            service_id=service_code, stop_id=row.part.stop.atco_code,
                            direction=grouping.direction, order=i, timing_status=row.part.timingstatus
                        )
                        for i, row in enumerate(grouping.rows) if row.part.stop.atco_code in stops
                    ]
                    if grouping.direction == 'outbound' or grouping.direction == 'inbound':
                        # grouping.description_parts = transxchange.description_parts
                        defaults[grouping.direction + '_description'] = str(grouping)

            line_strings = []
            for pattern in transxchange.journey_patterns.values():
                line_string = line_string_from_journeypattern(pattern, stops)
                if line_string not in line_strings:
                    line_strings.append(line_string)
            multi_line_string = MultiLineString(*(ls for ls in line_strings if ls))

        except (AttributeError, IndexError) as error:
            logger.error(error, exc_info=True)
            defaults['show_timetable'] = False
            stop_usages = [StopUsage(service_id=service_code, stop_id=stop, order=0) for stop in stops]
            multi_line_string = None

        defaults['geometry'] = multi_line_string

        if self.service_descriptions:
            defaults['outbound_description'], defaults['inbound_description'] = self.get_service_descriptions(filename)
            defaults['description'] = defaults['outbound_description'] or defaults['inbound_description']

        service, service_created = Service.objects.update_or_create(service_code=service_code, defaults=defaults)

        operators = self.get_operators(transxchange)

        if service_created:
            service.operator.add(*operators)
        else:
            if service.slug == service_code.lower():
                service.slug = ''
                service.save()
            service.operator.set(operators)
            if service_code not in self.service_codes:
                service.stops.clear()
        StopUsage.objects.bulk_create(stop_usages)

        # a code used in Traveline Cymru URLs:

        if transxchange.journeys and transxchange.journeys[0].private_code:
            private_code = transxchange.journeys[0].private_code
            if ':' in private_code:
                ServiceCode.objects.update_or_create({
                    'code': private_code.split(':', 1)[0]
                }, service=service, scheme='Traveline Cymru')

        # timetable data:

        route_defaults = {
            'line_name': line_name,
            'line_brand': line_brand,
            'start_date': transxchange.operating_period.start,
            'end_date': transxchange.operating_period.end,
            'service': service,
        }
        if 'description' in defaults:
            route_defaults['description'] = defaults['description']

        route, route_created = Route.objects.get_or_create(route_defaults, source=self.source, code=filename)

        default_calendar = None

        for journey in transxchange.journeys:
            calendar = None
            if journey.operating_profile:
                calendar = self.get_calendar(journey.operating_profile, transxchange.operating_period)
                if not calendar:
                    continue
            else:
                if not default_calendar:
                    default_calendar = self.get_calendar(transxchange.operating_profile, transxchange.operating_period)
                calendar = default_calendar

            trip = Trip(
                inbound=journey.journey_pattern.direction == 'inbound',
                calendar=calendar,
                route=route,
                journey_pattern=journey.journey_pattern.id,
            )

            stop_times = [
                StopTime(
                    stop_code=cell.stopusage.stop.atco_code,
                    trip=trip,
                    arrival=cell.arrival_time,
                    departure=cell.departure_time,
                    sequence=i,
                    timing_status=cell.stopusage.timingstatus,
                    activity=cell.stopusage.activity or ''
                ) for i, cell in enumerate(journey.get_times())
            ]

            trip.start = stop_times[0].arrival or stop_times[0].departure
            trip.end = stop_times[-1].departure or stop_times[-1].arrival

            if stop_times[-1].stop_code in stops:
                trip.destination_id = stop_times[-1].stop_code
                trip.save()
            else:
                print(stop_times[-1].stop_code, service)
                if stop_times[-2].stop_code in stops:
                    trip.destination_id = stop_times[-2].stop_code
                    trip.save()
                else:
                    print(stop_times[-2].stop_code, service)
                    return

            for note in journey.notes:
                note, _ = Note.objects.get_or_create(code=note, text=journey.notes[note])
                trip.notes.add(note)

            for stop_time in stop_times:
                stop_time.trip = stop_time.trip  # set trip_id
            StopTime.objects.bulk_create(stop_times)

        if service_code in self.corrections:
            corrections = {}
            for field in self.corrections[service_code]:
                if field == 'operator':
                    service.operator.set(self.corrections[service_code][field])
                else:
                    corrections[field] = self.corrections[service_code][field]
            Service.objects.filter(service_code=service_code).update(**corrections)