itinero / routing

The routing core of itinero.
Apache License 2.0
222 stars 70 forks source link

Train Profile (LUA or C#) #297

Closed juliusfriedman closed 4 years ago

juliusfriedman commented 4 years ago

Looking further into the source code it seems that it should be possible to route on rail or subway simply making different profiles which describe the links and paths which can be traversed on that profile. e.g. how bikes can ride on track yet cars cannot.

I think the only thing which need to be mainly changed is the wording of the exceptions to use the nomenclature from the profile rather than simply saying road in the exception e.g.

Could not resolve point at [52.74539, -1.13073]. Probably too far from closest road or outside of the loaded network

To

Could not resolve point at [52.74539, -1.13073]. Probably too far from closest track or outside of the loaded network

Where the "road" or "track" would come from the lua profile e.g. @ ResolveAlgoithmn.cs @ 113

if (edgeIds[0] == Constants.NO_EDGE) { // oeps, no edge was found, too far from road network. this.ErrorMessage = string.Format("Could not resolve point at [{0}, {1}]. Probably too far from closest road or outside of the loaded network.", _latitude.ToInvariantString(), _longitude.ToInvariantString()); return; }

But would need the profile names left as a formattable string so that when the algorithmn returns the profile could format the resolver.ErrorMessage appropriately. One could also use less verbose terminology such as edge and it would also suit multiple profiles.

Please see my attempt at an additional profile for train train.lua.txt

public class Train : Itinero.Profiles.Vehicle
    {
        static FactorAndSpeed DefaultFactorAndSpeed = new FactorAndSpeed()
        {
            SpeedFactor = 1 / (50f / 3.6f),
                Value = 1 / (50f / 3.6f),
                Direction = 0
            };

        readonly string[] _vehicleTypes = new[] { "train" };

        public Train()
        {
            this.Register(new Profile("shortest", ProfileMetric.DistanceInMeters, _vehicleTypes, null, this));
            this.Register(new Profile(string.Empty, ProfileMetric.TimeInSeconds, _vehicleTypes, null, this));
        }

        /// <summary>
        /// Gets the name of this vehicle.
        /// </summary>
        public override string Name => nameof(Train);

        /// <summary>
        /// Gets the vehicle types.
        /// </summary>
        public override string[] VehicleTypes
        {
            get
            {
                return _vehicleTypes;
            }
        }

        public override FactorAndSpeed FactorAndSpeed(IAttributeCollection attributes, Whitelist whitelist)
        {
            if(attributes != null)
            {
                attributes.Clear();
                attributes.AddOrReplace(new Itinero.Attributes.Attribute("rail", "primary"));

            }

            if (whitelist != null)
            {
                whitelist.Add("rail");
            }

            return DefaultFactorAndSpeed;
        }
    }

I have taken an attempt to using the following LUA to create a profile which can be used with out having to transform highway to rail.

Looking at the source code for Itinero it seems it should be possible as the nodes and ways are built using the information in the LUA profiles (OR the C# profiles)

Is there anyway you would be able to help me do this so that we don't first have to pre-process osm.pbf files to drop all highway and then convert rail to highway?

If you can show me how to do the Train I should be able to do the subway and contribute it back although I am sure you would be able to also provide a subway lua :)

thank you

juliusfriedman commented 4 years ago

I took another shot at my train example this time attempting to filter our unusable track

-- train globals
name = "train"
vehicle_types = { "vehicle", "motor_vehicle", "motorcar", "train" }
constraints =  { "maxweight", "maxwidth" }

minspeed = 30
maxspeed = 200

-- default speed profiles
speed_profile = {
    ["rail"] = 120,
    ["light_rail"] = 120,
    ["railway"] = 120,
    ["railway_link"] = 120,
    ["motorway"] = 120,
    ["motorway_link"] = 120,
    ["trunk"] = 90,
    ["trunk_link"] = 90,
    ["primary"] = 90,
    ["primary_link"] = 90,
    ["secondary"] = 70,
    ["secondary_link"] = 70,
    ["tertiary"] = 70,
    ["tertiary_link"] = 70,
    ["unclassified"] = 50,
    ["residential"] = 50,
    ["service"] = 30,
    ["services"] = 30,
    ["road"] = 30,
    ["track"] = 30,
    ["living_street"] = 5,
    ["ferry"] = 5,
    ["movable"] = 5,
    ["shuttle_train"] = 10,
    ["default"] = 10
}

-- default access values
access_values = {
    ["private"] = true,
    ["yes"] = true,
    ["no"] = true,
    ["permissive"] = true,
    ["destination"] = true,
    ["customers"] = false,
    ["designated"] = true,
    ["public"] = true,
    ["delivery"] = true,
    ["use_sidepath"] = false
}

-- whitelists for profile and meta
profile_whitelist = {
    "highway",
    "rail",
    "railway",
    "ligh_rail",
    "oneway",
    "motorcar",
    "motor_vehicle",
    "vehicle",
    "access",
    "maxspeed",
    "junction",
    "route",
    "barrier"
}
meta_whitelist = {
    "name",
    "bridge",
    "tunnel"
}

-- profile definitions linking a function to a profile
profiles = {
    {
        name = "",
        function_name = "factor_and_speed",
        metric = "time"
    },
    { 
        name = "shortest",
        function_name = "factor_and_speed",
        metric = "distance",
    },
    {
        name = "classifications",
        function_name = "factor_and_speed_classifications",
        metric = "custom"
    }
}

barriers = {
    ["gate"] = true,
    ["bollard"] = true,
    ["fence"] = true
}

--tags of rail which are not able to be traversed
unusable = {
    ["disused"] = true,
    ["razed"] = true,
    ["dismantled"] = true,
    ["demolished"] = true,
    ["abandoned"] = true,
    ["tram"] = true,
    ["proposed"] = true,
}

-- interprets node restrictions
function node_restriction (attributes, results)
    results.attributes_to_keep = {}

    local barrier = attributes.barrier
    if barrier == nil then
        return
    end

    local barrier_type = barriers[barrier]
    if barrier_type == nil then
        return
    end    
    results.attributes_to_keep.barrier = barrier    
    results.vehicle = "train"
end

-- interprets access tags
function can_access (attributes, result)
    local last_access = nil
    local access = access_values[attributes.access]
    if access != nil then
        result.attributes_to_keep.access = true
        last_access = access
    end
    for i=0, 10 do
        local access_key_key = vehicle_types[i]
        local access_key = attributes[access_key_key]
        if access_key then
            access = access_values[access_key]
            if access != nil then
                result.attributes_to_keep[access_key_key] = true
                last_access = access
            end
        end
    end
    return last_access
end

-- turns a oneway tag value into a direction
function is_oneway (attributes, name)
    local oneway = attributes[name]
    if oneway != nil then
        if oneway == "yes" or 
           oneway == "true" or 
           oneway == "1" then
            return 1
        end
        if oneway == "-1" then
            return 2
        end
    end
    return nil
end

-- the main function turning attributes into a factor_and_speed and a tag whitelist
function factor_and_speed (attributes, result)  

    local highway = attributes.highway

     result.speed = 0
     result.direction = 0
     result.canstop = true
     result.attributes_to_keep = {}

     -- set to rail when route is train or rail
     local route = attributes.route;
     if route == "train" or
        route == "rail" then
        highway = "rail"
        result.attributes_to_keep.route = highway       
     end

     -- do not consider unusable track
     if unusable[attributes.railway] then
        return
     end

     -- get default speed profiles
     local highway_speed = speed_profile[highway]
     if highway_speed then
        result.speed = highway_speed * 0.75
        result.direction = 0
        result.canstop = true
        result.attributes_to_keep.highway = highway
        if highway == "railway" or 
           highway == "railway_link" or
           highway == "rail" then
           result.canstop = false
        end
     else
        return
     end

     -- interpret access tags
     if can_access (attributes, result) == false then
        result.speed = 0
        result.direction = 0
        result.canstop = true
        return
     end

     -- get maxspeed if any.
     if attributes.maxspeed then
        local speed = itinero.parsespeed (attributes.maxspeed)
        if speed then
            result.speed = speed * 0.75
            result.attributes_to_keep.maxspeed = true
        end
    end

    -- get directional information
    local junction = attributes.junction
    if junction == "roundabout" then
        result.direction = 1
        result.attributes_to_keep.junction = true
    end
    local direction = is_oneway (attributes, "oneway")
    if direction != nil then
        result.direction = direction
        result.attributes_to_keep.oneway = true
    end
end

-- multiplication factors per classification
classifications_factors = {
    ["rail"] = 10,
    ["railway"] = 10,
    ["light_rail"] = 10,
    ["motorway"] = 10,
    ["motorway_link"] = 10,
    ["trunk"] = 9,
    ["trunk_link"] = 9,
    ["primary"] = 8,
    ["primary_link"] = 8,
    ["secondary"] = 7,
    ["secondary_link"] = 7,
    ["tertiary"] = 6,
    ["tertiary_link"] = 6,
    ["unclassified"] = 5,
    ["residential"] = 5
}

-- the classifications function for the classifications profile
function factor_and_speed_classifications (attributes, result)

    factor_and_speed (attributes, result)

    if result.speed == 0 then
        return
    end

    result.factor = 1.0 / (result.speed / 3.6)
    local classification_factor = classifications_factors[attributes.highway]
    if classification_factor != nil then
        result.factor = result.factor / classification_factor
    else
        result.factor = result.factor / 4
    end
end

-- instruction generators
instruction_generators = {
    {
        applies_to = "", -- applies to all profiles when empty
        generators = {
            {
                name = "start",
                function_name = "get_start"
            },
            { 
                name = "stop",
                function_name = "get_stop"
            },
            {
                name = "roundabout",
                function_name = "get_roundabout"
            },
            {
                name = "turn",
                function_name = "get_turn"
            }
        }
    }
}

-- gets the first instruction
function get_start (route_position, language_reference, instruction)
    if route_position.is_first() then
        local direction = route_position.direction()
        instruction.text = itinero.format(language_reference.get("Start {0}."), language_reference.get(direction));
        instruction.shape = route_position.shape
        return 1
    end
    return 0
end

-- gets the last instruction
function get_stop (route_position, language_reference, instruction) 
    if route_position.is_last() then
        instruction.text = language_reference.get("Arrived at destination.");
        instruction.shape = route_position.shape
        return 1
    end
    return 0
end

function contains (attributes, key, value)
    if attributes then
        return localvalue == attributes[key];
    end 
end

-- gets a roundabout instruction
function get_roundabout (route_position, language_reference, instruction) 
    if route_position.attributes.junction == "roundabout" and
        (not route_position.is_last()) then
        local attributes = route_position.next().attributes
        if attributes.junction then
        else
            local exit = 1
            local count = 1
            local previous = route_position.previous()
            while previous and previous.attributes.junction == "roundabout" do
                local branches = previous.branches
                if branches then
                    branches = branches.get_traversable()
                    if branches.count > 0 then
                        exit = exit + 1
                    end
                end
                count = count + 1
                previous = previous.previous()
            end

            instruction.text = itinero.format(language_reference.get("Take the {0}th exit at the next roundabout."), "" .. exit)
            if exit == 1 then
                instruction.text = itinero.format(language_reference.get("Take the first exit at the next roundabout."))
            elseif exit == 2 then
                instruction.text = itinero.format(language_reference.get("Take the second exit at the next roundabout."))
            elseif exit == 3 then
                instruction.text = itinero.format(language_reference.get("Take the third exit at the next roundabout."))
            end
            instruction.type = "roundabout"
            instruction.shape = route_position.shape
            return count
        end
    end
    return 0
end

-- gets a turn
function get_turn (route_position, language_reference, instruction) 
    local relative_direction = route_position.relative_direction().direction

    local turn_relevant = false
    local branches = route_position.branches
    if branches then
        branches = branches.get_traversable()
        if relative_direction == "straighton" and
            branches.count >= 2 then
            turn_relevant = true -- straight on at cross road
        end
        if  relative_direction != "straighton" and 
            branches.count > 0 then
            turn_relevant = true -- an actual normal turn
        end
    end

    if relative_direction == "unknown" then
        turn_relevant = false -- turn could not be calculated.
    end

    if turn_relevant then
        local next = route_position.next()
        local name = nil
        if next then
            name = next.attributes.name
        end
        if name then
            instruction.text = itinero.format(language_reference.get("Go {0} on {1}."), 
                language_reference.get(relative_direction), name)
            instruction.shape = route_position.shape
        else
            instruction.text = itinero.format(language_reference.get("Go {0}."), 
                language_reference.get(relative_direction))
            instruction.shape = route_position.shape
        end

        return 1
    end
    return 0
end

I am going by what I find at https://wiki.openstreetmap.org/wiki/Railways https://wiki.openstreetmap.org/wiki/Tag:route%3Dtrain#Network_by_countries

Overall it seems that there needs to be 3 or 4 different profiles

1 for Train 1 for Subway 1 for Tram 1 for Monorail (unless covered by tram)

Train is most important for me and my current project but I imagine the others would be useful as well and if you can help me with Train I can try to help with the rest.

that osm.pbf file was obtained from http://download.geofabrik.de/north-america.html

I filter the osm.pbf file by using the following command:

osmium tags-filter -o na-rail-latest.osm.pbf north-america-latest.osm.pbf nw/railway --progress

This leaves me only nodes and ways with railway so I should be able to create a LUA profile which can be routed on using a custom profile, I just need the profile help to get it to work as I have already tested by re-writing from railway to highway and that works fine.

Any assistance you can provide would be super helpful!

Thanks in advance!

juliusfriedman commented 4 years ago

Furthermore I think C# profiles are broken or simply poorly documented

Consider this

public class Train : Vehicle
        {
            static FactorAndSpeed DefaultFactorAndSpeed = new FactorAndSpeed()
            {
                Constraints = new[] { 1.0f },
                SpeedFactor = 1,
                Direction = 1,
                Value = 1
            };

            public override string Name => nameof(Train);

            public override FactorAndSpeed FactorAndSpeed(IAttributeCollection attributes, Whitelist whitelist)
            {
                return DefaultFactorAndSpeed;
            }
        }

When attempting to use this implementation I get:

System.IndexOutOfRangeException: 'Index was outside the bounds of the array.'

"   at Itinero.IO.Osm.Normalizer.AttributeCollectionExtensions.NormalizeAccess(IAttributeCollection tags, IAttributeCollection profileTags, Boolean defaultAccess, String[] accessTags) in ...`

This is because the line:

profileTags.AddOrReplace(accessTags[accessTags.Length - 1], "no");

Has 0 values and I am not sure how to populate accessTags in the attribute collection.

Even something like this doesn't work:

public class Train : Vehicle
        {
            public Train()
            {
                MetaWhiteList.Add("railway");
                MetaWhiteList.Add("light_rail");

                ProfileWhiteList.Add("");
                ProfileWhiteList.Add("rail");
            }

            static FactorAndSpeed DefaultFactorAndSpeed = new FactorAndSpeed()
            {
                Constraints = new[] { 1.0f },
                SpeedFactor = 1,
                Direction = 1,
                Value = 1
            };

            static string[] VehicleTypesArray = new[] { "train" };

            public override string Name => nameof(Train);

            public override string[] VehicleTypes => VehicleTypesArray;

            public override FactorAndSpeed FactorAndSpeed(IAttributeCollection attributes, Whitelist whitelist)
            {                
                return DefaultFactorAndSpeed;
            }
        }

Doesn't work because the Fastest method passes string,empty as the value which I have no idea how to populate from the C# class. This results in KeyNotFoundException because _profiles of the Vehicle class Train has 0 length and I have no idea how to populate that from the given examples.

I get around this by registering profiles in the constructor of Train like so:

Register(new Profile("shortest", ProfileMetric.DistanceInMeters, VehicleTypes, null, this));
                Register(new Profile(string.Empty, ProfileMetric.TimeInSeconds, VehicleTypes, null, this));

But surprisingly enough I can't seem to find any RouterPoints or ways so I am cornfused.

Any help you would be able to provide would be great!

Thank you!

juliusfriedman commented 4 years ago

Looking at the linked issue their LUA seems like it works to route on trains, I modified it slightly to not consider rails which are demolished or otherwise.

This seems to work but I am wondering if I need to also handle spur, siding etc or if this will suffice.

If this is good enough than why was this removed from Itinero?

-- train globals
name = "train"
vehicle_types = { "vehicle", "motor_vehicle", "motorcar" }
constraints =  { "maxweight", "maxwidth" }

minspeed = 30
maxspeed = 200

-- default speed profiles
speed_profile = {
    ["rail"] = 60,
    ["default"] = 60
}

-- default access values
access_values = {
    ["private"] = false,
    ["yes"] = true,
    ["no"] = false,
    ["permissive"] = true,
    ["destination"] = true,
    ["customers"] = false,
    ["designated"] = true,
    ["public"] = true,
    ["delivery"] = true,
    ["use_sidepath"] = false
}

--tags of rail which are not able to be traversed
unusable = {
    ["disused"] = true,
    ["razed"] = true,
    ["dismantled"] = true,
    ["demolished"] = true,
    ["abandoned"] = true,
    ["tram"] = true,
    ["proposed"] = true,
}

-- whitelists for profile and meta
profile_whitelist = {
    "railway"
}
meta_whitelist = {
    "name"
}

-- profile definitions linking a function to a profile
profiles = {
    {
        name = "",
        function_name = "factor_and_speed",
        metric = "time"
    },
    { 
        name = "shortest",
        function_name = "factor_and_speed",
        metric = "distance",
    },
    {
        name = "classifications",
        function_name = "factor_and_speed_classifications",
        metric = "custom"
    }
}

-- interprets access tags
function can_access (attributes, result)
    local last_access = nil
    local access = access_values[attributes.access]
    if access != nil then
        result.attributes_to_keep.access = true
        last_access = access
    end
    for i=0, 10 do
        local access_key_key = vehicle_types[i]
        local access_key = attributes[access_key_key]
        if access_key then
            access = access_values[access_key]
            if access != nil then
                result.attributes_to_keep[access_key_key] = true
                last_access = access
            end
        end
    end
    return last_access
end

-- turns a oneway tag value into a direction
function is_oneway (attributes, name)
    local oneway = attributes[name]
    if oneway != nil then
        if oneway == "yes" or 
           oneway == "true" or 
           oneway == "1" then
            return 1
        end
        if oneway == "-1" then
            return 2
        end
    end
    return nil
end

-- the main function turning attributes into a factor_and_speed and a tag whitelist
function factor_and_speed (attributes, result)
     local railway = attributes.railway

     result.speed = 0
     result.direction = 0
     result.canstop = true
     result.attributes_to_keep = {}

      -- do not consider unusable track
     if unusable[attributes.railway] then
        return
     end

     -- get default speed profiles
     local railway_speed = speed_profile[railway]
     if railway_speed then
        result.speed = railway_speed
        result.direction = 0
        result.canstop = true
        result.attributes_to_keep.railway = railway
        if railway == "motorway" or 
           railway == "motorway_link" then
           result.canstop = false
        end
     else
        return
     end

     -- interpret access tags
     if can_access (attributes, result) == false then
        result.speed = 0
        result.direction = 0
        result.canstop = true
        return
     end

     -- get maxspeed if any.
     if attributes.maxspeed then
        local speed = itinero.parsespeed (attributes.maxspeed)
        if speed then
            result.speed = speed * 0.75
            result.attributes_to_keep.maxspeed = true
        end
    end

    -- get maxweight and maxwidth constraints if any
    local maxweight = 0
    local maxwidth = 0
    if attributes.maxweight then
        maxweight = itinero.parseweight (attributes.maxweight)
    end
    if attributes.maxwidth then
        maxwidth = itinero.parseweight (attributes.maxwidth)
    end
    if maxwidth != 0 or maxweight != 0 then
        result.constraints = { maxweight, maxwidth }
        result.attributes_to_keep.maxweight = true
        result.attributes_to_keep.maxwidth = true
    end

    -- get directional information
    local junction = attributes.junction
    if junction == "roundabout" then
        result.direction = 1
    end
    local direction = is_oneway (attributes, "oneway")
    if direction != nil then
        result.direction = direction
    end
end

-- multiplication factors per classification
classifications_factors = {
    ["rail"] = 5
}

-- the classifications function for the classifications profile
function factor_and_speed_classifications (attributes, result)

    factor_and_speed (attributes, result)

    if result.speed == 0 then
        return
    end

    result.factor = 1.0 / (result.speed / 3.6)
    local classification_factor = classifications_factors[attributes.railway]
    if classification_factor != nil then
        result.factor = result.factor / classification_factor
    else
        result.factor = result.factor / 4
    end
end

-- instruction generators
instruction_generators = {
    {
        applies_to = "", -- applies to all profiles when empty
        generators = {
            {
                name = "start",
                function_name = "get_start"
            },
            { 
                name = "stop",
                function_name = "get_stop"
            },
            {
                name = "roundabout",
                function_name = "get_roundabout"
            },
            {
                name = "turn",
                function_name = "get_turn"
            }
        }
    }
}

-- gets the first instruction
function get_start (route_position, language_reference, instruction)
    if route_position.is_first() then
        local direction = route_position.direction()
        instruction.text = itinero.format(language_reference.get("Start {0}."), language_reference.get(direction));
        instruction.shape = route_position.shape
        return 1
    end
    return 0
end

-- gets the last instruction
function get_stop (route_position, language_reference, instruction) 
    if route_position.is_last() then
        instruction.text = language_reference.get("Arrived at destination.");
        instruction.shape = route_position.shape
        return 1
    end
    return 0
end

function contains (attributes, key, value)
    if attributes then
        return localvalue == attributes[key];
    end 
end

-- gets a roundabout instruction
function get_roundabout (route_position, language_reference, instruction) 
    if route_position.attributes.junction == "roundabout" and
        (not route_position.is_last()) then
        local attributes = route_position.next().attributes
        if attributes.junction then
        else
            local exit = 1
            local count = 1
            local previous = route_position.previous()
            while previous and previous.attributes.junction == "roundabout" do
                local branches = previous.branches
                if branches then
                    branches = branches.get_traversable()
                    if branches.count > 0 then
                        exit = exit + 1
                    end
                end
                count = count + 1
                previous = previous.previous()
            end

            instruction.text = itinero.format(language_reference.get("Take the {0}th exit at the next roundabout."), "" .. exit)
            if exit == 1 then
                instruction.text = itinero.format(language_reference.get("Take the first exit at the next roundabout."))
            elseif exit == 2 then
                instruction.text = itinero.format(language_reference.get("Take the second exit at the next roundabout."))
            elseif exit == 3 then
                instruction.text = itinero.format(language_reference.get("Take the third exit at the next roundabout."))
            end
            instruction.type = "roundabout"
            instruction.shape = route_position.shape
            return count
        end
    end
    return 0
end

-- gets a turn
function get_turn (route_position, language_reference, instruction) 
    local relative_direction = route_position.relative_direction().direction

    local turn_relevant = false
    local branches = route_position.branches
    if branches then
        branches = branches.get_traversable()
        if relative_direction == "straighton" and
            branches.count >= 2 then
            turn_relevant = true -- straight on at cross road
        end
        if  relative_direction != "straighton" and 
            branches.count > 0 then
            turn_relevant = true -- an actual normal turn
        end
    end

    if turn_relevant then
        local next = route_position.next()
        local name = nil
        if next then
            name = next.attributes.name
        end
        if name then
            instruction.text = itinero.format(language_reference.get("Go {0} on {1}."), 
                language_reference.get(relative_direction), name)
            instruction.shape = route_position.shape
        else
            instruction.text = itinero.format(language_reference.get("Go {0}."), 
                language_reference.get(relative_direction))
            instruction.shape = route_position.shape
        end

        return 1
    end
    return 0
end
juliusfriedman commented 4 years ago

Here is a slightly updated version which also allows crossovers and spurs and sidings to be stopped on I think train.lua.txt

And here is the C# code complete which should be able to at least get a position

public class Train : Profiles.Vehicle
        {
            public Train()
            {
                Register(new Profile("shortest", ProfileMetric.DistanceInMeters, VehicleTypes, null, this));
                Register(new Profile(string.Empty, ProfileMetric.TimeInSeconds, VehicleTypes, null, this));
                MetaWhiteList.Add("name");
                ProfileWhiteList.Add("railway");
            }

            static FactorAndSpeed DefaultFactorAndSpeed = new FactorAndSpeed()
            {
                Constraints = new[] { 1.0f },
                SpeedFactor = 1,
                Direction = 1,
                Value = 1
            };

            static string[] VehicleTypesArray = new[] { "vehicle", "motor_vehicle", "motorcar" };

            public override string Name => nameof(Train);

            public override string[] VehicleTypes => VehicleTypesArray;

            public override FactorAndSpeed FactorAndSpeed(IAttributeCollection attributes, Whitelist whitelist)
            {                
                return DefaultFactorAndSpeed;
            }
        }

I am still not sure why the same c# code can't get a position but it seems the train.lua file can.

Any help you would be able to provide to improve this will help and I will see about trams monorail as separate profiles if you wish.

I can also see about enhancing to make sure track types are the same e.g. electrified vs non etc but again some help would be needed to get me going.

Once Train / Rail is solidified I can work further to have profiles for Tram, Subway, ElectricTrain etc

This is very similar to what https://github.com/geofabrik/OpenRailRouting can do in Java and is likely the only C# alternative which can work that I have found.

xivk commented 4 years ago

I have done this type of routing before in this repository to extra shapes for GTFS feeds from OSM:

https://github.com/itinero/GTFS/tree/features/shapes/src/GTFS.Shapes

There are some example profiles that may help:

https://github.com/itinero/GTFS/tree/features/shapes/src/GTFS.Shapes/Osm/profiles

I will be getting back into this soon as we are again working with GTFS data and again the shapes are missing. What is your goal with this project?

Apologies for not being more responsive, trying to keep up with other work has been tough.

xivk commented 4 years ago

You can also calculate routing islands, to prevent a snapping to a part of the network that is not connect to the rest:

       // add island data.
        routerDb.AddIslandData(profile);

Once you've done that Itinero should know these part of the network are islands and it should detect what parts of the network are connected.

xivk commented 4 years ago

Using the profile in the repo I sent I manage to calculate this route:

http://geojson.io/#id=gist:xivk/ca34ab29d3f09a33a346c1ed301e6e90&map=12/51.1169/4.4588

juliusfriedman commented 4 years ago

Thank you for getting back to me!

My goals are using this project to provide length and transit estimates to enhance the data provided by my contractors sensors.

E.g. they have sensors on a train and they need to know given various points what the likely route was and then along with that information we know the actual speed and heading so we can augment that to provide ETA and various other services.

Thank you for your help.

I will let you know more what I find through the weekend and definitely next week.

juliusfriedman commented 4 years ago

Your profile seems to work the same way as mine did only it seems it doesn't check speeds so I am going to use that as the simple profile by default and use mine for other things... I am going to close this issue. Thank you for your help!