diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 00000000..fc480664 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: f4e0af575c62fbd746eba8909e9e8c9a +tags: d77d1c0d9ca2f4c8421862c7c5a0d620 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/_generated/network_wrangler.ProjectCard/index.html b/_generated/network_wrangler.ProjectCard/index.html new file mode 100644 index 00000000..a3b65965 --- /dev/null +++ b/_generated/network_wrangler.ProjectCard/index.html @@ -0,0 +1,385 @@ + + + + + + + network_wrangler.ProjectCard — Network Wrangler documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

network_wrangler.ProjectCard

+
+
+class network_wrangler.ProjectCard(attribute_dictonary)[source]
+

Bases: object

+

Representation of a Project Card

+
+
+__dict__
+

Dictionary of project card attributes

+
+ +
+
+valid
+

Boolean indicating if data conforms to project card data schema

+
+ +
+
+__init__(attribute_dictonary)[source]
+

Constructor for Project Card object.

+
+
Parameters:
+

attribute_dictonary – a nested dictionary of project card attributes.

+
+
+
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

__init__(attribute_dictonary)

Constructor for Project Card object.

build_link_selection_query(selection, ...[, ...])

One line description #todo #239 #238

new_roadway(card)

Probably delete.

new_transit_right_of_way(card)

Probably delete.

parallel_managed_lanes(card)

Probably delete.

read(card_filename[, validate])

Reads and validates a Project card

read_wrangler_card(w_card_filename)

Reads wrangler project cards with YAML front matter and then python code.

read_yml(card_filename)

Reads "normal" wrangler project cards defined in YAML.

roadway_attribute_change(card)

Probably delete.

transit_attribute_change(card)

Probably delete.

validate_project_card_schema(card_filename)

Tests project card schema validity by evaluating if it conforms to the schemas

write([out_filename])

Writes project card dictionary to YAML file.

+

Attributes

+ + + + + + + + + + + + +

ROADWAY_CATEGORIES

SECONDARY_TRANSIT_CATEGORIES

TRANSIT_CATEGORIES

+
+ +

One line description +#todo #239 #238

+
+
Parameters:
+
    +
  • selection

  • +
  • unique_link_ids

  • +
  • mode

  • +
  • ignore

  • +
+
+
+

returns:

+

usage

+
+ +
+
+new_roadway(card)[source]
+

Probably delete. +Reads a New Roadway card.

+

args: +card: the project card stored in a dictionary

+
+ +
+
+new_transit_right_of_way(card)[source]
+

Probably delete. +Reads a New Transit Dedicated Right of Way card.

+

args: +card: the project card stored in a dictionary

+
+ +
+
+parallel_managed_lanes(card)[source]
+

Probably delete. +Reads a Parallel Managed lanes card.

+

args: +card: the project card stored in a dictionary

+
+ +
+
+static read(card_filename, validate=True)[source]
+

Reads and validates a Project card

+
+
Parameters:
+
    +
  • card_filename – The path to the project card file.

  • +
  • validate – Boolean indicating if the project card should be validated. Defaults to True.

  • +
+
+
+

Returns a Project Card object

+
+ +
+
+static read_wrangler_card(w_card_filename)[source]
+

Reads wrangler project cards with YAML front matter and then python code.

+
+
Return type:
+

dict

+
+
Parameters:
+

w_card_filename – where the project card is

+
+
+

Returns: Attribute Dictionary for Project Card

+
+ +
+
+static read_yml(card_filename)[source]
+

Reads “normal” wrangler project cards defined in YAML.

+
+
Return type:
+

dict

+
+
Parameters:
+

card_filename – file location where the project card is.

+
+
+

Returns: Attribute Dictionary for Project Card

+
+ +
+
+roadway_attribute_change(card)[source]
+

Probably delete. +Reads a Roadway Attribute Change card.

+

args: +card: the project card stored in a dictionary

+
+ +
+
+transit_attribute_change(card)[source]
+

Probably delete. +Reads a Transit Service Attribute Change card.

+

args: +card: the project card stored in a dictionary

+
+ +
+
+static validate_project_card_schema(card_filename, card_schema_filename='project_card.json')[source]
+

Tests project card schema validity by evaluating if it conforms to the schemas

+
+
Return type:
+

bool

+
+
Parameters:
+
    +
  • card_filename – location of project card .yml file

  • +
  • card_schema_filename – location of project card schema to validate against. Defaults to project_card.json.

  • +
+
+
+

returns: boolean

+
+ +
+
+write(out_filename=None)[source]
+

Writes project card dictionary to YAML file.

+
+
Parameters:
+

out_filename – file location to write the project card object as yml. +If not provided, will write to current directory using the project name as the filename.

+
+
+
+ +
+
+ROADWAY_CATEGORIES = ['Roadway Property Change', 'Roadway Deletion', 'Parallel Managed lanes', 'Add New Roadway', 'Calculated Roadway']
+
+ +
+
+SECONDARY_TRANSIT_CATEGORIES = ['Roadway Deletion', 'Parallel Managed Lanes']
+
+ +
+
+TRANSIT_CATEGORIES = ['Transit Service Property Change', 'Add Transit']
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_generated/network_wrangler.RoadwayNetwork/index.html b/_generated/network_wrangler.RoadwayNetwork/index.html new file mode 100644 index 00000000..a03da5b1 --- /dev/null +++ b/_generated/network_wrangler.RoadwayNetwork/index.html @@ -0,0 +1,1339 @@ + + + + + + + network_wrangler.RoadwayNetwork — Network Wrangler documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

network_wrangler.RoadwayNetwork

+
+
+class network_wrangler.RoadwayNetwork(nodes, links, shapes=None, node_foreign_key=None, link_foreign_key=None, shape_foreign_key=None, unique_link_key=None, unique_node_key=None, unique_link_ids=None, unique_node_ids=None, crs=None, **kwargs)[source]
+

Bases: object

+

Representation of a Roadway Network.

+

Typical usage example:

+
net = RoadwayNetwork.read(
+    link_filename=MY_LINK_FILE,
+    node_filename=MY_NODE_FILE,
+    shape_filename=MY_SHAPE_FILE,
+    shape_foreign_key ='shape_id',
+)
+my_selection = {
+    "link": [{"name": ["I 35E"]}],
+    "A": {"osm_node_id": "961117623"},  # start searching for segments at A
+    "B": {"osm_node_id": "2564047368"},
+}
+net.select_roadway_features(my_selection)
+
+my_change = [
+    {
+        'property': 'lanes',
+        'existing': 1,
+        'set': 2,
+     },
+     {
+        'property': 'drive_access',
+        'set': 0,
+      },
+]
+
+my_net.apply_roadway_feature_change(
+    my_net.select_roadway_features(my_selection),
+    my_change
+)
+
+ml_net = net.create_managed_lane_network(in_place=False)
+ml_net.is_network_connected(mode="drive"))
+_, disconnected_nodes = ml_net.assess_connectivity(mode="walk", ignore_end_nodes=True)
+ml_net.write(filename=my_out_prefix, path=my_dir)
+
+
+
+
+nodes_df
+

node data

+
+
Type:
+

GeoDataFrame

+
+
+
+ +
+ +

link data, including start and end +nodes and associated shape

+
+
Type:
+

GeoDataFrame

+
+
+
+ +
+
+shapes_df
+

detailed shape data

+
+
Type:
+

GeoDataFrame

+
+
+
+ +
+
+crs
+

coordinate reference system, ESPG number

+
+
Type:
+

int

+
+
+
+ +
+
+node_foreign_key
+

variable linking the node table to the link table

+
+
Type:
+

str

+
+
+
+ +
+ +

list of variable linking the link table to the node foreign key

+
+
Type:
+

list

+
+
+
+ +
+
+shape_foreign_key
+

variable linking the links table and shape table

+
+
Type:
+

str

+
+
+
+ +
+ +

list of variables unique to each link

+
+
Type:
+

list

+
+
+
+ +
+
+unique_node_ids
+

list of variables unique to each node

+
+
Type:
+

list

+
+
+
+ +
+ +

Mapping of modes to link variables in the network

+
+
Type:
+

dict

+
+
+
+ +
+
+modes_to_network_nodes_variables
+

Mapping of modes to node variables in the network

+
+
Type:
+

dict

+
+
+
+ +
+
+managed_lanes_node_id_scalar
+

Scalar values added to primary keys for nodes for +corresponding managed lanes.

+
+
Type:
+

int

+
+
+
+ +
+ +

Scalar values added to primary keys for links for +corresponding managed lanes.

+
+
Type:
+

int

+
+
+
+ +
+
+managed_lanes_required_attributes
+

attributes that must be specified in managed +lane projects.

+
+
Type:
+

list

+
+
+
+ +
+
+keep_same_attributes_ml_and_gp
+

attributes to copy to managed lanes from parallel +general purpose lanes.

+
+
Type:
+

list

+
+
+
+ +
+
+selections
+

dictionary storing selections in case they are made repeatedly

+
+
Type:
+

dict

+
+
+
+ +
+
+__init__(nodes, links, shapes=None, node_foreign_key=None, link_foreign_key=None, shape_foreign_key=None, unique_link_key=None, unique_node_key=None, unique_link_ids=None, unique_node_ids=None, crs=None, **kwargs)[source]
+

Constructor

+
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

__init__(nodes, links[, shapes, ...])

Constructor

add_incident_link_data_to_nodes([links_df, ...])

Add data from links going to/from nodes to node.

add_new_roadway_feature_change(links, nodes)

add the new roadway features defined in the project card.

addition_map(links, nodes)

Shows which links and nodes are added to the roadway network

apply(project_card_dictionary)

Wrapper method to apply a project to a roadway network.

apply_managed_lane_feature_change(link_idx, ...)

Apply the managed lane feature changes to the roadway network

apply_python_calculation(pycode[, in_place])

Changes roadway network object by executing pycode.

apply_roadway_feature_change(link_idx, ...)

Changes the roadway attributes for the selected features based on the project card information passed

assess_connectivity([mode, ...])

Returns a network graph and list of disconnected subgraphs as described by a list of their member nodes.

build_selection_key(selection_dict)

Selections are stored by a key combining the query and the A and B ids.

create_dummy_connector_links(ml_df[, ...])

create dummy connector links between the general purpose and managed lanes

create_managed_lane_network([...])

Create a roadway network with managed lanes links separated out.

delete_roadway_feature_change(links, nodes)

delete the roadway features defined in the project card.

deletion_map(links, nodes)

Shows which links and nodes are deleted from the roadway network

get_managed_lane_node_ids(nodes_list[, scalar])

Transform a list of node IDS by a scalar.

get_modal_graph(links_df, nodes_df[, mode, ...])

Determines if the network graph is "strongly" connected A graph is strongly connected if each vertex is reachable from every other vertex.

get_modal_links_nodes(links_df, nodes_df[, ...])

Returns nodes and link dataframes for specific mode.

get_property_by_time_period_and_group(prop)

Return a series for the properties with a specific group or time period.

identify_segment(O_id, D_id[, ...])

+
param endpoints:
+

list of length of two unique keys of nodes making up endpoints of segment

+
+
+

identify_segment_endpoints([mode, links_df, ...])

+
param mode:
+

list of modes of the network, one of drive,`transit`,

+
+
+

is_network_connected([mode, links_df, nodes_df])

Determines if the network graph is "strongly" connected A graph is strongly connected if each vertex is reachable from every other vertex.

load_transform_network(node_filename, ...[, ...])

Reads roadway network files from disk and transforms them into GeoDataFrames.

network_connection_plot(G, ...)

Plot a graph to check for network connection.

orig_dest_nodes_foreign_key(selection[, ...])

Returns the foreign key id (whatever is used in the u and v variables in the links file) for the AB nodes as a tuple.

ox_graph(nodes_df, links_df[, ...])

create an osmnx-flavored network graph

path_search(candidate_links_df, O_id, D_id)

+
param candidate_links:
+

selection of links geodataframe with links likely to be part of path

+
+
+

read(link_filename, node_filename, ...[, ...])

Reads a network from the roadway network standard Validates that it conforms to the schema

roadway_net_to_gdf(roadway_net)

+
rtype:
+

GeoDataFrame

+
+
+

select_roadway_features(selection[, ...])

Selects roadway features that satisfy selection criteria

selection_has_unique_link_id(selection_dict)

+
rtype:
+

bool

+
+
+

selection_map(selected_link_idx[, A, B, ...])

Shows which links are selected for roadway property change or parallel managed lanes category of roadway projects.

shortest_path(graph_links_df, O_id, D_id[, ...])

+
rtype:
+

tuple

+
+
+

update_distance([links_df, use_shapes, ...])

Calculate link distance in specified units to network variable using either straight line distance or (if specified) shape distance if available.

validate_link_schema(link_filename[, ...])

Validate roadway network data link schema and output a boolean

validate_node_schema(node_file[, ...])

Validate roadway network data node schema and output a boolean

validate_properties(properties[, ...])

If there are change or existing commands, make sure that that property exists in the network.

validate_selection(selection[, ...])

Evaluate whetther the selection dictionary contains the minimum required values.

validate_shape_schema(shape_file[, ...])

Validate roadway network data shape schema and output a boolean

validate_uniqueness()

Confirms that the unique identifiers are met.

write([path, filename])

Writes a network in the roadway network standard

+
+ +

Add data from links going to/from nodes to node.

+
+
Return type:
+

DataFrame

+
+
Parameters:
+
    +
  • links_df – if specified, will assess connectivity of this +links list rather than self.links_df

  • +
  • nodes_df – if specified, will assess connectivity of this +nodes list rather than self.nodes_df

  • +
  • link_variables – list of columns in links dataframe to add to incident nodes

  • +
+
+
Returns:
+

nodes DataFrame with link data where length is N*number of links going in/out

+
+
+
+ +
+
+add_new_roadway_feature_change(links, nodes)[source]
+

add the new roadway features defined in the project card. +new shapes are also added for the new roadway links.

+
+
Return type:
+

None

+
+
Parameters:
+
    +
  • links – list of dictionaries

  • +
  • nodes – list of dictionaries

  • +
+
+
+

returns: None

+
+

Todo

+

validate links and nodes dictionary

+
+
+ +
+
+addition_map(links, nodes)[source]
+

Shows which links and nodes are added to the roadway network

+
+ +
+
+apply(project_card_dictionary)[source]
+

Wrapper method to apply a project to a roadway network.

+
+
Parameters:
+

project_card_dictionary – dict +a dictionary of the project card object

+
+
+
+ +
+
+apply_managed_lane_feature_change(link_idx, properties, in_place=True)[source]
+

Apply the managed lane feature changes to the roadway network

+
+
Parameters:
+
    +
  • link_idx – list of lndices of all links to apply change to

  • +
  • properties – list of dictionarys roadway properties to change

  • +
  • in_place – boolean to indicate whether to update self or return +a new roadway network object

  • +
+
+
+
+

Todo

+

decide on connectors info when they are more specific in project card

+
+
+ +
+
+apply_python_calculation(pycode, in_place=True)[source]
+

Changes roadway network object by executing pycode.

+
+
Parameters:
+
    +
  • pycode – python code which changes values in the roadway network object

  • +
  • in_place – update self or return a new roadway network object

  • +
+
+
+
+ +
+
+apply_roadway_feature_change(link_idx, properties, in_place=True)[source]
+

Changes the roadway attributes for the selected features based on the +project card information passed

+
+
Parameters:
+
    +
  • link_idx – list +lndices of all links to apply change to

  • +
  • properties – list of dictionarys +roadway properties to change

  • +
  • in_place – boolean +update self or return a new roadway network object

  • +
+
+
+
+ +
+
+assess_connectivity(mode='', ignore_end_nodes=True, links_df=None, nodes_df=None)[source]
+

Returns a network graph and list of disconnected subgraphs +as described by a list of their member nodes.

+
+
Parameters:
+
    +
  • mode – list of modes of the network, one of drive,`transit`, +walk, bike

  • +
  • ignore_end_nodes – if True, ignores stray singleton nodes

  • +
  • links_df – if specified, will assess connectivity of this +links list rather than self.links_df

  • +
  • nodes_df – if specified, will assess connectivity of this +nodes list rather than self.nodes_df

  • +
+
+
+
+
Returns: Tuple of

Network Graph (osmnx flavored networkX DiGraph) +List of disconnected subgraphs described by the list of their

+
+

member nodes (as described by their model_node_id)

+
+
+
+
+ +
+
+build_selection_key(selection_dict)[source]
+

Selections are stored by a key combining the query and the A and B ids. +This method combines the two for you based on the selection dictionary.

+
+
Return type:
+

tuple

+
+
Parameters:
+

selection_dictonary – Selection Dictionary

+
+
+

Returns: Tuple serving as the selection key.

+
+ +
+ +

create dummy connector links between the general purpose and managed lanes

+
+
Parameters:
+
    +
  • gp_df – GeoDataFrame +dataframe of general purpose links (where managed lane also exists)

  • +
  • ml_df – GeoDataFrame +dataframe of corresponding managed lane links,

  • +
  • access_lanes – int +number of lanes in access dummy link

  • +
  • egress_lanes – int +number of lanes in egress dummy link

  • +
  • access_roadway – str +roaday type for access dummy link

  • +
  • egress_roadway – str +roadway type for egress dummy link

  • +
  • access_name_prefix – str +prefix for access dummy link name

  • +
  • egress_name_prefix – str +prefix for egress dummy link name

  • +
+
+
+
+ +
+
+create_managed_lane_network(keep_same_attributes_ml_and_gp=None, keep_additional_attributes_ml_and_gp=[], managed_lanes_required_attributes=[], managed_lanes_node_id_scalar=None, managed_lanes_link_id_scalar=None, in_place=False)[source]
+

Create a roadway network with managed lanes links separated out. +Add new parallel managed lane links, access/egress links, +and add shapes corresponding to the new links

+
+
Return type:
+

RoadwayNetwork

+
+
Parameters:
+
    +
  • keep_same_attributes_ml_and_gp – list of attributes to copy from general purpose +lane to managed lane. If not specified, will look for value in the RoadwayNetwork +instance. If not found there, will default to KEEP_SAME_ATTRIBUTES_ML_AND_GP.

  • +
  • keep_additional_attributes_ml_and_gp – list of additional attributes to add. This is useful +if you want to leave the default attributes and then ALSO some others.

  • +
  • managed_lanes_required_attributes – list of attributes that are required to be specified +in new managed lanes. If not specified, will look for value in the RoadwayNetwork +instance. If not found there, will default to MANAGED_LANES_REQUIRED_ATTRIBUTES.

  • +
  • managed_lanes_node_id_scalar – integer value added to original node IDs to create managed +lane unique ids. If not specified, will look for value in the RoadwayNetwork +instance. If not found there, will default to MANAGED_LANES_NODE_ID_SCALAR.

  • +
  • managed_lanes_link_id_scalar – integer value added to original link IDs to create managed +lane unique ids. If not specified, will look for value in the RoadwayNetwork +instance. If not found there, will default to MANAGED_LANES_LINK_ID_SCALAR.

  • +
  • in_place – update self or return a new roadway network object

  • +
+
+
+

returns: A RoadwayNetwork instance

+
+

Todo

+

make this a more rigorous test

+
+
+ +
+
+delete_roadway_feature_change(links, nodes, ignore_missing=True)[source]
+

delete the roadway features defined in the project card. +valid links and nodes defined in the project gets deleted +and shapes corresponding to the deleted links are also deleted.

+
+
Return type:
+

None

+
+
Parameters:
+
    +
  • links – dict +list of dictionaries

  • +
  • nodes – dict +list of dictionaries

  • +
  • ignore_missing – bool +If True, will only warn about links/nodes that are missing from +network but specified to “delete” in project card +If False, will fail.

  • +
+
+
+
+ +
+
+deletion_map(links, nodes)[source]
+

Shows which links and nodes are deleted from the roadway network

+
+ +
+
+static get_managed_lane_node_ids(nodes_list, scalar=4500000)[source]
+

Transform a list of node IDS by a scalar. +..todo #237 what if node ids are not a number?

+
+
Parameters:
+
    +
  • nodes_list – list of integers

  • +
  • scalar – value to add to node IDs

  • +
+
+
+

Returns: list of integers

+
+ +
+
+static get_modal_graph(links_df, nodes_df, mode=None, modes_to_network_link_variables={'bike': ['bike_access'], 'bus': ['bus_only', 'drive_access'], 'drive': ['drive_access'], 'rail': ['rail_only'], 'transit': ['bus_only', 'rail_only', 'drive_access'], 'walk': ['walk_access']})[source]
+

Determines if the network graph is “strongly” connected +A graph is strongly connected if each vertex is reachable from every other vertex.

+
+
Parameters:
+
    +
  • links_df – DataFrame of standard network links

  • +
  • nodes_df – DataFrame of standard network nodes

  • +
  • mode – mode of the network, one of drive,`transit`, +walk, bike

  • +
  • modes_to_network_link_variables – dictionary mapping the mode selections to the network variables +that must bool to true to select that mode. Defaults to MODES_TO_NETWORK_LINK_VARIABLES

  • +
+
+
+

Returns: networkx: osmnx: DiGraph of network

+
+ +
+ +

Returns nodes and link dataframes for specific mode.

+
+
Parameters:
+
    +
  • links_df – DataFrame of standard network links

  • +
  • nodes_df – DataFrame of standard network nodes

  • +
  • modes – list of the modes of the network to be kept, must be in drive,`transit`,`rail`,`bus`, +walk, bike. For example, if bike and walk are selected, both bike and walk links will be kept.

  • +
  • modes_to_network_link_variables – dictionary mapping the mode selections to the network variables +that must bool to true to select that mode. Defaults to MODES_TO_NETWORK_LINK_VARIABLES

  • +
+
+
+

Returns: tuple of DataFrames for links, nodes filtered by mode

+
+

Todo

+

Right now we don’t filter the nodes because transit-only

+
+

links with walk access are not marked as having walk access +Issue discussed in https://github.com/wsp-sag/network_wrangler/issues/145 +modal_nodes_df = nodes_df[nodes_df[mode_node_variable] == 1]

+
+ +
+
+get_property_by_time_period_and_group(prop, time_period=None, category=None, default_return=None)[source]
+

Return a series for the properties with a specific group or time period.

+
+
Parameters:
+
    +
  • prop (str) – the variable that you want from network

  • +
  • time_period (list(str)) – the time period that you are querying for +i.e. [‘16:00’, ‘19:00’]

  • +
  • category (str or list(str)(Optional)) –

    the group category +i.e. “sov”

    +

    or

    +

    list of group categories in order of search, i.e. +[“hov3”,”hov2”]

    +

  • +
  • default_return (what to return if variable or time period not found. Default is None.) –

  • +
+
+
Return type:
+

pandas series

+
+
+
+ +
+
+identify_segment(O_id, D_id, selection_dict={}, mode=None, nodes_df=None, links_df=None)[source]
+
+
Parameters:
+
    +
  • endpoints – list of length of two unique keys of nodes making up endpoints of segment

  • +
  • selection_dict – dictionary of link variables to select candidate links from, otherwise will create a graph of ALL links which will be both a RAM hog and could result in odd shortest paths.

  • +
  • segment_variables – list of variables to keep

  • +
+
+
+
+ +
+
+identify_segment_endpoints(mode='', links_df=None, nodes_df=None, min_connecting_links=10, min_distance=None, max_link_deviation=2)[source]
+
+
Parameters:
+
    +
  • mode – list of modes of the network, one of drive,`transit`, +walk, bike

  • +
  • links_df – if specified, will assess connectivity of this +links list rather than self.links_df

  • +
  • nodes_df – if specified, will assess connectivity of this +nodes list rather than self.nodes_df

  • +
+
+
+
+ +
+
+is_network_connected(mode=None, links_df=None, nodes_df=None)[source]
+

Determines if the network graph is “strongly” connected +A graph is strongly connected if each vertex is reachable from every other vertex.

+
+
Parameters:
+
    +
  • mode – mode of the network, one of drive,`transit`, +walk, bike

  • +
  • links_df – DataFrame of standard network links

  • +
  • nodes_df – DataFrame of standard network nodes

  • +
+
+
+

Returns: boolean

+
+

Todo

+

Consider caching graphs if they take a long time.

+
+
+ +
+
+static load_transform_network(node_filename, link_filename, shape_filename, crs=4326, node_foreign_key='model_node_id', validate_schema=True, **kwargs)[source]
+

Reads roadway network files from disk and transforms them into GeoDataFrames.

+
+
Return type:
+

tuple

+
+
Parameters:
+
    +
  • node_filename – file name for nodes.

  • +
  • link_filename – file name for links.

  • +
  • shape_filename – file name for shapes.

  • +
  • crs – coordinate reference system. Defaults to value in CRS.

  • +
  • node_foreign_key – variable linking the node table to the link table. Defaults +to NODE_FOREIGN_KEY.

  • +
  • validate_schema – boolean indicating if network should be validated to schema.

  • +
+
+
+

returns: tuple of GeodataFrames nodes_df, links_df, shapes_df

+
+ +
+
+static network_connection_plot(G, disconnected_subgraph_nodes)[source]
+

Plot a graph to check for network connection.

+
+
Parameters:
+
    +
  • G – OSMNX flavored networkX graph.

  • +
  • disconnected_subgraph_nodes – List of disconnected subgraphs described by the list of their +member nodes (as described by their model_node_id).

  • +
+
+
+

returns: fig, ax : tuple

+
+ +
+
+orig_dest_nodes_foreign_key(selection, node_foreign_key='')[source]
+

Returns the foreign key id (whatever is used in the u and v +variables in the links file) for the AB nodes as a tuple.

+
+
Return type:
+

tuple

+
+
Parameters:
+
    +
  • selection – selection dictionary with A and B keys

  • +
  • node_foreign_key – variable name for whatever is used by the u and v variable

  • +
  • specified (in the links_df file. If nothing is) –

  • +
  • whatever (assume) –

  • +
  • is (default) –

  • +
+
+
+

Returns: tuple of (A_id, B_id)

+
+ +
+
+static ox_graph(nodes_df, links_df, node_foreign_key='model_node_id', link_foreign_key=['A', 'B'], unique_link_key='model_link_id')[source]
+

create an osmnx-flavored network graph

+

osmnx doesn’t like values that are arrays, so remove the variables +that have arrays. osmnx also requires that certain variables +be filled in, so do that too.

+
+
Parameters:
+
    +
  • nodes_df – GeoDataFrame of nodes

  • +
  • link_df – GeoDataFrame of links

  • +
  • node_foreign_key – field referenced in link_foreign_key

  • +
  • link_foreign_key – list of attributes that define the link start and end nodes to the node foreign key

  • +
  • unique_link_key – primary key for links

  • +
+
+
+

Returns: a networkx multidigraph

+
+ +
+ +
+
Parameters:
+
    +
  • candidate_links – selection of links geodataframe with links likely to be part of path

  • +
  • O_id – origin node foreigh key ID

  • +
  • D_id – destination node foreigh key ID

  • +
  • weight_column – column to use for weight of shortest path. Defaults to “i” (iteration)

  • +
  • weight_factor – optional weight to multiply the weight column by when finding the shortest path

  • +
  • search_breadth

  • +
+
+
+

Returns

+
+ +
+
+static read(link_filename, node_filename, shape_filename, fast=True, crs=4326, node_foreign_key='model_node_id', link_foreign_key=['A', 'B'], shape_foreign_key='id', unique_link_key='model_link_id', unique_node_key='model_node_id', unique_link_ids=['model_link_id'], unique_node_ids=['model_node_id'], modes_to_network_link_variables={'bike': ['bike_access'], 'bus': ['bus_only', 'drive_access'], 'drive': ['drive_access'], 'rail': ['rail_only'], 'transit': ['bus_only', 'rail_only', 'drive_access'], 'walk': ['walk_access']}, modes_to_network_nodes_variables={'bike': ['bike_node'], 'bus': ['bus_only', 'drive_access'], 'drive': ['drive_access'], 'rail': ['rail_only', 'drive_access'], 'transit': ['bus_only', 'rail_only', 'drive_access'], 'walk': ['walk_node']}, managed_lanes_link_id_scalar=10000000, managed_lanes_node_id_scalar=4500000, managed_lanes_required_attributes=['A', 'B', 'model_link_id', 'locationReferences'], keep_same_attributes_ml_and_gp=['distance', 'bike_access', 'drive_access', 'transit_access', 'walk_access', 'maxspeed', 'name', 'oneway', 'ref', 'roadway', 'length', 'segment_id', 'ft', 'assignable', 'county'])[source]
+

Reads a network from the roadway network standard +Validates that it conforms to the schema

+
+
Return type:
+

RoadwayNetwork

+
+
Parameters:
+
    +
  • node_filename – full path to the node file

  • +
  • link_filename – full path to the link file

  • +
  • shape_filename – full path to the shape file

  • +
  • fast – boolean that will skip validation to speed up read time

  • +
  • crs – coordinate reference system, ESPG number

  • +
  • node_foreign_key – variable linking the node table to the link table.

  • +
  • link_foreign_key

  • +
  • shape_foreign_key

  • +
  • unique_link_ids

  • +
  • unique_node_ids

  • +
  • modes_to_network_link_variables

  • +
  • modes_to_network_nodes_variables

  • +
  • managed_lanes_node_id_scalar

  • +
  • managed_lanes_link_id_scalar

  • +
  • managed_lanes_required_attributes

  • +
  • keep_same_attributes_ml_and_gp

  • +
+
+
+

Returns: a RoadwayNetwork instance

+
+

Todo

+

Turn off fast=True as default

+
+
+ +
+
+static roadway_net_to_gdf(roadway_net)[source]
+
+
Return type:
+

GeoDataFrame

+
+
+

Turn the roadway network into a GeoDataFrame +:param roadway_net: the roadway network to export

+

returns: shapes dataframe

+
+

Todo

+

Make this much more sophisticated, for example attach link info to shapes

+
+
+ +
+
+select_roadway_features(selection, search_mode='drive', force_search=False, sp_weight_factor=None)[source]
+

Selects roadway features that satisfy selection criteria

+
+
Return type:
+

GeoDataFrame

+
+
+
+
Example usage:
+
net.select_roadway_features(
+
selection = [ {

# a match condition for the from node using osm, +# shared streets, or model node number +‘from’: {‘osm_model_link_id’: ‘1234’}, +# a match for the to-node.. +‘to’: {‘shstid’: ‘4321’}, +# a regex or match for facility condition +# could be # of lanes, facility type, etc. +‘facility’: {‘name’:’Main St’}, +}, … ])

+
+
+
+
+
+
+
+
Parameters:
+
    +
  • selection – dictionary with keys for: +A - from node +B - to node +link - which includes at least a variable for name

  • +
  • search_mode – mode which you are searching for; defaults to “drive”

  • +
  • force_search – boolean directing method to perform search even if one +with same selection dict is stored from a previous search.

  • +
  • sp_weight_factor – multiple used to discourage shortest paths which +meander from original search returned from name or ref query. +If not set here, will default to value of sp_weight_factor in +RoadwayNetwork instance. If not set there, will defaul to SP_WEIGHT_FACTOR.

  • +
+
+
+

Returns: a list of link IDs in selection

+
+ +
+ +
+
Return type:
+

bool

+
+
Parameters:
+

selection_dictionary – Dictionary representation of selection +of roadway features, containing a “link” key.

+
+
+
+
Returns: A boolean indicating if the selection dictionary contains

a unique identifier for links.

+
+
+
+ +
+
+selection_map(selected_link_idx, A=None, B=None, candidate_link_idx=[])[source]
+

Shows which links are selected for roadway property change or parallel +managed lanes category of roadway projects.

+
+
Parameters:
+
    +
  • selected_links_idx – list of selected link indices

  • +
  • candidate_links_idx – optional list of candidate link indices to also include in map

  • +
  • A – optional foreign key of starting node of a route selection

  • +
  • B – optional foreign key of ending node of a route selection

  • +
+
+
+
+ +
+
+shortest_path(graph_links_df, O_id, D_id, nodes_df=None, weight_column='i', weight_factor=100)[source]
+
+
Return type:
+

tuple

+
+
Parameters:
+
    +
  • graph_links_df

  • +
  • O_id – foreign key for start node

  • +
  • D_id – foreign key for end node

  • +
  • nodes_df – optional nodes df, otherwise will use network instance

  • +
  • weight_column – column to use as a weight, defaults to “i”

  • +
  • weight_factor – any additional weighting to multiply the weight column by, defaults to SP_WEIGHT_FACTOR

  • +
+
+
+

Returns: tuple with length of four +- Boolean if shortest path found +- nx Directed graph of graph links +- route of shortest path nodes as List +- links in shortest path selected from links_df

+
+ +
+
+update_distance(links_df=None, use_shapes=False, units='miles', network_variable='distance', overwrite=True, inplace=True)[source]
+

Calculate link distance in specified units to network variable using either straight line +distance or (if specified) shape distance if available.

+
+
Parameters:
+
    +
  • links_df – Links GeoDataFrame. Useful if want to update a portion of network links +(i.e. only centroid connectors). If not provided, will use entire self.links_df.

  • +
  • use_shapes – if True, will add length information from self.shapes_df rather than crow-fly. +If no corresponding shape found in self.shapes_df, will default to crow-fly.

  • +
  • units – units to use. Defaults to the standard unit of miles. Available units: “meters”, “miles”.

  • +
  • network_variable – variable to store link distance in. Defaults to “distance”.

  • +
  • overwrite – Defaults to True and will overwrite all existing calculated distances. +False will only update NaNs.

  • +
  • inplace – updates self.links_df

  • +
+
+
Returns:
+

links_df with updated distance

+
+
+
+ +
+ +

Validate roadway network data link schema and output a boolean

+
+ +
+
+static validate_node_schema(node_file, schema_location='roadway_network_node.json')[source]
+

Validate roadway network data node schema and output a boolean

+
+ +
+
+validate_properties(properties, ignore_existing=False, require_existing_for_change=False)[source]
+

If there are change or existing commands, make sure that that +property exists in the network.

+
+
Return type:
+

bool

+
+
Parameters:
+
    +
  • properties – properties dictionary to be evaluated

  • +
  • ignore_existing – If True, will only warn about properties +that specify an “existing” value. If False, will fail.

  • +
  • require_existing_for_change – If True, will fail if there isn’t +a specified value in theproject card for existing when a +change is specified.

  • +
+
+
+

Returns: boolean value as to whether the properties dictonary is valid.

+
+ +
+
+validate_selection(selection, selection_requires=['link'])[source]
+

Evaluate whetther the selection dictionary contains the +minimum required values.

+
+
Return type:
+

bool

+
+
Parameters:
+

selection – selection dictionary to be evaluated

+
+
+

Returns: boolean value as to whether the selection dictonary is valid.

+
+ +
+
+static validate_shape_schema(shape_file, schema_location='roadway_network_shape.json')[source]
+

Validate roadway network data shape schema and output a boolean

+
+ +
+
+validate_uniqueness()[source]
+

Confirms that the unique identifiers are met.

+
+
Return type:
+

bool

+
+
+
+ +
+
+write(path='.', filename=None)[source]
+

Writes a network in the roadway network standard

+
+
Return type:
+

None

+
+
Parameters:
+
    +
  • path – the path were the output will be saved

  • +
  • filename – the name prefix of the roadway files that will be generated

  • +
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_generated/network_wrangler.Scenario/index.html b/_generated/network_wrangler.Scenario/index.html new file mode 100644 index 00000000..2b0b03b9 --- /dev/null +++ b/_generated/network_wrangler.Scenario/index.html @@ -0,0 +1,524 @@ + + + + + + + network_wrangler.Scenario — Network Wrangler documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

network_wrangler.Scenario

+
+
+class network_wrangler.Scenario(base_scenario, project_cards=None)[source]
+

Bases: object

+

Holds information about a scenario.

+

Typical usage example:

+
my_base_scenario = {
+    "road_net": RoadwayNetwork.read(
+        link_filename=STPAUL_LINK_FILE,
+        node_filename=STPAUL_NODE_FILE,
+        shape_filename=STPAUL_SHAPE_FILE,
+        fast=True,
+    ),
+    "transit_net": TransitNetwork.read(STPAUL_DIR),
+}
+
+card_filenames = [
+    "3_multiple_roadway_attribute_change.yml",
+    "multiple_changes.yml",
+    "4_simple_managed_lane.yml",
+]
+
+project_card_directory = os.path.join(STPAUL_DIR, "project_cards")
+
+project_cards_list = [
+    ProjectCard.read(os.path.join(project_card_directory, filename), validate=False)
+    for filename in card_filenames
+]
+
+my_scenario = Scenario.create_scenario(
+  base_scenario=my_base_scenario,
+  project_cards_list=project_cards_list,
+)
+my_scenario.check_scenario_requisites()
+
+my_scenario.apply_all_projects()
+
+my_scenario.scenario_summary()
+
+
+
+
+base_scenario
+

dictionary representation of a scenario

+
+ +
+
+project_cards
+

list of Project Card Instances

+
+
Type:
+

Optional

+
+
+
+ +
+
+road_net
+

instance of RoadwayNetwork for the scenario

+
+ +
+
+transit_net
+

instance of TransitNetwork for the scenario

+
+ +
+
+applied_projects
+

list of project names that have been applied

+
+ +
+
+project_cards
+

list of project card instances

+
+ +
+
+ordered_project_cards
+
+ +
+
+prerequisites
+

dictionary storing prerequiste information

+
+ +
+
+corequisites
+

dictionary storing corequisite information

+
+ +
+
+conflicts
+

dictionary storing conflict information

+
+ +
+
+requisites_checked
+

boolean indicating if the co- and pre-requisites +have been checked in the project cards

+
+ +
+
+conflicts_checked
+

boolean indicating if the project conflicts have been checked

+
+ +
+
+has_requisite_error
+

boolean indicating if there is a conflict in the pre- or +co-requisites of project cards

+
+ +
+
+has_conflict_error
+

boolean indicating if there is are conflicting project cards

+
+ +
+
+prerequisites_sorted
+

boolean indicating if the project cards have +been sorted to make sure cards that are pre-requisites are applied first

+
+ +
+
+__init__(base_scenario, project_cards=None)[source]
+

Constructor

+

args: +base_scenario: dict the base scenario +project_cards: list this scenario’s project cards

+
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

__init__(base_scenario[, project_cards])

Constructor

add_project_card_from_file(project_card_filename)

add_project_cards_from_directory(folder[, ...])

Adds projects cards to the scenario.

add_project_cards_from_tags(folder[, tags, ...])

Adds projects cards to the scenario.

applied_project_card_summary(...)

Create a summary of applied project card and what they changed for the scenario.

apply_all_projects()

apply_project(p)

check_scenario_conflicts()

Checks if there are any conflicting projects in the scenario Fail if the project A specifies that project B is a conflict and project B is included in the scenario

check_scenario_requisites()

Checks if there are any missing pre- or co-requisite projects in the scenario Fail if the project A specifies that project B is a pre- or co-requisite and project B is not included in the scenario

create_base_scenario(base_shape_name, ...[, ...])

+
rtype:
+

Scenario

+
+
+

create_scenario([base_scenario, ...])

Validates project cards with a specific tag from the specified folder or list of user specified project cards and creates a scenario object with the valid project card.

get_project_names()

Returns a list of project names

order_project_cards()

create a list of project cards such that they are in order based on pre-requisites

remove_all_projects()

scenario_summary([project_detail, outfile, mode])

A high level summary of the created scenario.

+
+
+add_project_card_from_file(project_card_filename, validate=True, tags=[])[source]
+
+ +
+
+add_project_cards_from_directory(folder, glob_search='', validate=True)[source]
+

Adds projects cards to the scenario. +A folder is provided to look for project cards and if applicable, a glob-style search.

+

i.e. glob_search = ‘road*.yml’

+

args: +folder: the folder location where the project cards will be +glob_search: https://docs.python.org/2/library/glob.html

+
+ +
+
+add_project_cards_from_tags(folder, tags=[], glob_search='', validate=True)[source]
+

Adds projects cards to the scenario. +A folder is provided to look for project cards that have a matching tag that is passed to the method.

+

args: +folder: the folder location where the project cards will be +tags: only project cards with these tags will be validated and added to the returning scenario

+
+ +
+
+applied_project_card_summary(project_card_dictionary)[source]
+

Create a summary of applied project card and what they changed for the scenario.

+
+
Return type:
+

dict

+
+
Parameters:
+

project_card_dictionary – dictionary representation of the values of a project card (i.e. ProjectCard.__dict__ )

+
+
Returns:
+

A dict of project summary change dictionaries for each change

+
+
+
+ +
+
+apply_all_projects()[source]
+
+ +
+
+apply_project(p)[source]
+
+ +
+
+check_scenario_conflicts()[source]
+

Checks if there are any conflicting projects in the scenario +Fail if the project A specifies that project B is a conflict and project B is included in the scenario

+

Returns: boolean indicating if the check was successful or returned an error

+
+
Return type:
+

bool

+
+
+
+ +
+
+check_scenario_requisites()[source]
+

Checks if there are any missing pre- or co-requisite projects in the scenario +Fail if the project A specifies that project B is a pre- or co-requisite and project B is not included in the scenario

+

Returns: boolean indicating if the checks were successful or returned an error

+
+
Return type:
+

bool

+
+
+
+ +
+
+static create_base_scenario(base_shape_name, base_link_name, base_node_name, roadway_dir='', transit_dir='', validate=True, **kwargs)[source]
+
+
Return type:
+

Scenario

+
+
Parameters:
+
    +
  • roadway_dir (optional) – path to the base scenario roadway network files

  • +
  • base_shape_name – filename of the base network shape

  • +
  • base_link_name – filename of the base network link

  • +
  • base_node_name – filename of the base network node

  • +
  • transit_dir (optional) – path to base scenario transit files

  • +
  • validate – boolean indicating whether to validate the base network or not

  • +
+
+
+
+ +
+
+static create_scenario(base_scenario={}, card_directory='', tags=None, project_cards_list=[], glob_search='', validate_project_cards=True)[source]
+

Validates project cards with a specific tag from the specified folder or +list of user specified project cards and +creates a scenario object with the valid project card.

+
+
Parameters:
+
    +
  • base_scenario – object dictionary for the base scenario (i.e. my_base_scenario.__dict__)

  • +
  • tags – only project cards with these tags will be read/validated

  • +
  • folder – the folder location where the project cards will be

  • +
  • project_cards_list – list of project cards to be applied

  • +
  • glob_search

  • +
+
+
+
+ +
+
+get_project_names()[source]
+

Returns a list of project names

+
+
Return type:
+

list

+
+
+
+ +
+
+order_project_cards()[source]
+

create a list of project cards such that they are in order based on pre-requisites

+

Returns: ordered list of project cards to be applied to scenario

+
+ +
+
+remove_all_projects()[source]
+
+ +
+
+scenario_summary(project_detail=True, outfile='', mode='a')[source]
+

A high level summary of the created scenario.

+
+
Return type:
+

str

+
+
Parameters:
+
    +
  • project_detail – If True (default), will write out project card summaries.

  • +
  • outfile – If specified, will write scenario summary to text file.

  • +
  • mode – Outfile open mode. ‘a’ to append ‘w’ to overwrite.

  • +
+
+
Returns:
+

string of summary

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_generated/network_wrangler.TransitNetwork/index.html b/_generated/network_wrangler.TransitNetwork/index.html new file mode 100644 index 00000000..082a1dc9 --- /dev/null +++ b/_generated/network_wrangler.TransitNetwork/index.html @@ -0,0 +1,747 @@ + + + + + + + network_wrangler.TransitNetwork — Network Wrangler documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

network_wrangler.TransitNetwork

+
+
+class network_wrangler.TransitNetwork(feed=None, config=None, shapes_foreign_key=None, stops_foreign_key=None, id_scalar=None)[source]
+

Bases: object

+

Representation of a Transit Network.

+

Typical usage example:

+
import network_wrangler as wr
+stpaul = r'/home/jovyan/work/example/stpaul'
+tc=wr.TransitNetwork.read(path=stpaul)
+
+
+
+
+feed
+

Partridge feed mapping dataframes.

+
+
Type:
+

DotDict

+
+
+
+ +
+
+config
+

Partridge config

+
+
Type:
+

nx.DiGraph

+
+
+
+ +
+
+road_net
+

Associated roadway network object.

+
+
Type:
+

RoadwayNetwork

+
+
+
+ +
+
+graph
+

Graph for associated roadway network object.

+
+
Type:
+

nx.MultiDiGraph

+
+
+
+ +
+
+feed_path
+

Where the feed was read in from.

+
+
Type:
+

str

+
+
+
+ +
+
+validated_frequencies
+

The frequencies have been validated.

+
+
Type:
+

bool

+
+
+
+ +
+
+validated_road_network_consistency
+

The network has been validated against the road network.

+
+ +
+
+shapes_foreign_key
+

foreign key between shapes dataframe and roadway network nodes.

+
+
Type:
+

str

+
+
+
+ +
+
+stops_foreign_key
+

foreign key between stops dataframe and roadway network nodes.

+
+
Type:
+

str

+
+
+
+ +
+
+id_scalar
+

scalar value added to create new stop and shape IDs when necessary.

+
+
Type:
+

int

+
+
+
+ +
+
+REQUIRED_FILES
+

list of files that the transit network requires.

+
+
Type:
+

list[str]

+
+
+
+ +
+
+__init__(feed=None, config=None, shapes_foreign_key=None, stops_foreign_key=None, id_scalar=None)[source]
+

Constructor

+
+

Todo

+

Make graph a reference to associated RoadwayNetwork’s graph, not its own thing.

+
+
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

__init__([feed, config, shapes_foreign_key, ...])

Constructor

apply(project_card_dictionary)

Wrapper method to apply a project to a transit network.

apply_python_calculation(pycode[, in_place])

Changes roadway network object by executing pycode.

apply_transit_feature_change(trip_ids, ...)

Changes the transit attributes for the selected features based on the project card information passed

apply_transit_managed_lane(trip_ids, ...[, ...])

check_network_connectivity(shapes_foreign_key)

check if new shapes contain any links that are not in the roadway network

empty()

Create an empty transit network instance using the default config.

read(feed_path[, shapes_foreign_key, ...])

Read GTFS feed from folder and TransitNetwork object.

route_between_nodes(graph, A, B)

find complete path when the new shape has connectivity issue

route_ids_in_routestxt(feed)

Wherever route_id occurs, make sure it is in routes.txt

select_transit_features(selection)

combines multiple selections

select_transit_features_by_nodes(node_ids[, ...])

Selects transit features that use any one of a list of node_ids

set_roadnet(road_net[, graph_shapes, ...])

+
rtype:
+

None

+
+
+

shape_ids_in_shapestxt(feed)

Wherever shape_id occurs, make sure it is in shapes.txt

stop_ids_in_stopstxt(feed)

Wherever stop_id occurs, make sure it is in stops.txt

transit_net_to_gdf(transit)

Returns a geodataframe given a TransitNetwork or a valid Shapes DataFrame.

trip_ids_in_tripstxt(feed)

Wherever trip_id occurs, make sure it is in trips.txt

validate_feed(feed, config)

Since Partridge lazily loads the df, load each file to make sure it actually works.

validate_frequencies()

Validates that there are no transit trips in the feed with zero frequencies.

validate_network_keys(feed)

Validates foreign keys are present in all connecting feed files.

validate_road_network_consistencies()

Validates transit network against the road network for both stops and shapes.

validate_transit_shapes()

Validates that all transit shapes are part of the roadway network.

validate_transit_stops()

Validates that all transit stops are part of the roadway network.

write([path, filename])

Writes a network in the transit network standard

+

Attributes

+ + + + + + +

REQUIRED_FILES

+
+
+apply(project_card_dictionary)[source]
+

Wrapper method to apply a project to a transit network.

+
+
Parameters:
+

project_card_dictionary – dict +a dictionary of the project card object

+
+
+
+ +
+
+apply_python_calculation(pycode, in_place=True)[source]
+

Changes roadway network object by executing pycode.

+
+
Parameters:
+
    +
  • pycode – python code which changes values in the roadway network object

  • +
  • in_place – update self or return a new roadway network object

  • +
+
+
+
+ +
+
+apply_transit_feature_change(trip_ids, properties, in_place=True)[source]
+

Changes the transit attributes for the selected features based on the +project card information passed

+
+
Parameters:
+
    +
  • trip_ids – pd.Series +all trip_ids to apply change to

  • +
  • properties – list of dictionaries +transit properties to change

  • +
  • in_place – bool +whether to apply changes in place or return a new network

  • +
+
+
Returns:
+

None

+
+
+
+ +
+
+apply_transit_managed_lane(trip_ids, node_ids, scalar, in_place=True)[source]
+
+ +
+
+check_network_connectivity(shapes_foreign_key)[source]
+

check if new shapes contain any links that are not in the roadway network

+
+
Return type:
+

Series

+
+
+
+ +
+
+static empty()[source]
+

Create an empty transit network instance using the default config. +:rtype: TransitNetwork

+
+

Todo

+

fill out this method

+
+
+ +
+
+static read(feed_path, shapes_foreign_key='shape_model_node_id', stops_foreign_key='model_node_id', id_scalar=100000000)[source]
+

Read GTFS feed from folder and TransitNetwork object.

+
+
Return type:
+

TransitNetwork

+
+
Parameters:
+
    +
  • feed_path – where to read transit network files from.

  • +
  • shapes_foreign_key – foreign key between shapes dataframe and roadway network nodes. Will default to SHAPES_FOREIGN_KEY if not provided.

  • +
  • stops_foreign_key – foreign key between stops dataframe and roadway network nodes. Will defaul to STOPS_FOREIGN_KEY if not provided.

  • +
  • id_scalar – scalar value added to create new stop and shape IDs when necessary. Will default to ID_SCALAR if not provided.

  • +
+
+
+

Returns: a TransitNetwork object.

+
+ +
+
+static route_between_nodes(graph, A, B)[source]
+

find complete path when the new shape has connectivity issue

+
+
Return type:
+

list

+
+
+
+ +
+
+static route_ids_in_routestxt(feed)[source]
+

Wherever route_id occurs, make sure it is in routes.txt

+
+
Return type:
+

Bool

+
+
Parameters:
+

feed – partridge feed object

+
+
Returns:
+

Boolean indicating if feed is okay.

+
+
+
+ +
+
+select_transit_features(selection)[source]
+

combines multiple selections

+
+
Return type:
+

Series

+
+
Parameters:
+

selection – selection dictionary

+
+
+

Returns: trip identifiers : list of GTFS trip IDs in the selection

+
+ +
+
+select_transit_features_by_nodes(node_ids, require_all=False)[source]
+

Selects transit features that use any one of a list of node_ids

+
+
Return type:
+

Series

+
+
Parameters:
+
    +
  • node_ids – list (generally coming from nx.shortest_path)

  • +
  • require_all – bool if True, the returned trip_ids must traverse all of +the nodes (default = False)

  • +
+
+
Returns:
+

trip identifiers list of GTFS trip IDs in the selection

+
+
+
+ +
+
+set_roadnet(road_net, graph_shapes=False, graph_stops=False, validate_consistency=True)[source]
+
+
Return type:
+

None

+
+
+
+ +
+
+static shape_ids_in_shapestxt(feed)[source]
+

Wherever shape_id occurs, make sure it is in shapes.txt

+
+
Return type:
+

Bool

+
+
Parameters:
+

feed – partridge feed object

+
+
Returns:
+

Boolean indicating if feed is okay.

+
+
+
+ +
+
+static stop_ids_in_stopstxt(feed)[source]
+

Wherever stop_id occurs, make sure it is in stops.txt

+
+
Return type:
+

Bool

+
+
Parameters:
+

feed – partridge feed object

+
+
Returns:
+

Boolean indicating if feed is okay.

+
+
+
+ +
+
+static transit_net_to_gdf(transit)[source]
+

Returns a geodataframe given a TransitNetwork or a valid Shapes DataFrame.

+
+
Parameters:
+

transit – either a TransitNetwork or a Shapes GeoDataFrame

+
+
+
+

Todo

+

Make more sophisticated.

+
+
+ +
+
+static trip_ids_in_tripstxt(feed)[source]
+

Wherever trip_id occurs, make sure it is in trips.txt

+
+
Return type:
+

Bool

+
+
Parameters:
+

feed – partridge feed object

+
+
Returns:
+

Boolean indicating if feed is okay.

+
+
+
+ +
+
+static validate_feed(feed, config)[source]
+

Since Partridge lazily loads the df, load each file to make sure it +actually works.

+

Partridge uses a DiGraph from the networkx library to represent the +relationships between GTFS files. Each file is a ‘node’, and the +relationship between files are ‘edges’.

+
+
Return type:
+

bool

+
+
Parameters:
+
    +
  • feed – partridge feed

  • +
  • config – partridge config

  • +
+
+
+
+ +
+
+validate_frequencies()[source]
+

Validates that there are no transit trips in the feed with zero frequencies.

+

Changes state of self.validated_frequencies boolean based on outcome.

+
+
Return type:
+

bool

+
+
Returns:
+

boolean indicating if valid or not.

+
+
+
+ +
+
+static validate_network_keys(feed)[source]
+

Validates foreign keys are present in all connecting feed files.

+
+
Return type:
+

Bool

+
+
Parameters:
+

feed – partridge feed object

+
+
Returns:
+

Boolean indicating if feed is okay.

+
+
+
+ +
+
+validate_road_network_consistencies()[source]
+

Validates transit network against the road network for both stops +and shapes.

+
+
Return type:
+

bool

+
+
Returns:
+

boolean indicating if valid or not.

+
+
+
+ +
+
+validate_transit_shapes()[source]
+

Validates that all transit shapes are part of the roadway network.

+
+
Return type:
+

bool

+
+
Returns:
+

Boolean indicating if valid or not.

+
+
+
+ +
+
+validate_transit_stops()[source]
+

Validates that all transit stops are part of the roadway network.

+
+
Return type:
+

bool

+
+
Returns:
+

Boolean indicating if valid or not.

+
+
+
+ +
+
+write(path='.', filename=None)[source]
+

Writes a network in the transit network standard

+
+
Return type:
+

None

+
+
Parameters:
+
    +
  • path – the path were the output will be saved

  • +
  • filename – the name prefix of the transit files that will be generated

  • +
+
+
+
+ +
+
+REQUIRED_FILES = ['agency.txt', 'frequencies.txt', 'routes.txt', 'shapes.txt', 'stop_times.txt', 'stops.txt', 'trips.txt']
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_generated/network_wrangler.logger/index.html b/_generated/network_wrangler.logger/index.html new file mode 100644 index 00000000..46cc1764 --- /dev/null +++ b/_generated/network_wrangler.logger/index.html @@ -0,0 +1,156 @@ + + + + + + + network_wrangler.logger — Network Wrangler documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

network_wrangler.logger

+

Functions

+ + + + + + +

setupLogging([info_log_filename, ...])

Sets up the WranglerLogger w.r.t.

+
+
+network_wrangler.logger.setupLogging(info_log_filename=None, debug_log_filename=None, log_to_console=True)[source]
+

Sets up the WranglerLogger w.r.t. the debug file location and if logging to console.

+
+
Parameters:
+
    +
  • info_log_filename – the location of the log file that will get created to add the INFO log. +The INFO Log is terse, just gives the bare minimum of details.

  • +
  • debug_log_filename – the location of the log file that will get created to add the DEBUG log. +The DEBUG log is very noisy, for debugging.

  • +
  • log_to_console – if True, logging will go to the console at DEBUG level

  • +
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_generated/network_wrangler.utils/index.html b/_generated/network_wrangler.utils/index.html new file mode 100644 index 00000000..56b4fcc0 --- /dev/null +++ b/_generated/network_wrangler.utils/index.html @@ -0,0 +1,1440 @@ + + + + + + + network_wrangler.utils — Network Wrangler documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

network_wrangler.utils

+

Functions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

create_line_string(location_reference)

Creates a geometry as a LineString using location reference

create_location_reference_from_nodes(node_a, ...)

Creates a location reference using the node a and node b coordinates

create_unique_shape_id(line_string)

Creates a unique hash id using the coordinates of the geomtery

get_bearing(lat1, lon1, lat2, lon2)

calculate the bearing (forward azimuth) b/w the two points

haversine_distance(origin, destination[, units])

Calculates haversine distance between two points

link_df_to_json(df, properties)

Export pandas dataframe as a json object.

make_slug(text[, delimiter])

makes a slug from text

offset_location_reference(location_reference)

Creates a new location reference using the node a and node b of given location reference, offseting it by 90 degree to the bearing of given location reference and distance equals to offset_meters

offset_point_with_distance_and_bearing(lat, ...)

Get the new lat long (in degrees) given current point (lat/lon), distance and bearing

parse_time_spans(times)

parse time spans into tuples of seconds from midnight can also be used as an apply function for a pandas series :param times: :type times: tuple(string) or tuple(int) or list(string) or list(int)

point_df_to_geojson(df, properties[, ...])

Author: Geoff Boeing: https://geoffboeing.com/2015/10/exporting-python-data-geojson/

topological_sort(adjacency_list, visited_list)

Topological sorting for Acyclic Directed Graph

update_df(base_df, update_df[, merge_key, ...])

Updates specific fields of a dataframe with another dataframe using a key column.

+
+
+class network_wrangler.utils.Geodesic(a, f)[source]
+

Bases: object

+

Solve geodesic problems

+
+
+ArcDirect(lat1, lon1, azi1, a12, outmask=1929)[source]
+

Solve the direct geodesic problem in terms of spherical arc length

+
+
Parameters:
+
    +
  • lat1 – latitude of the first point in degrees

  • +
  • lon1 – longitude of the first point in degrees

  • +
  • azi1 – azimuth at the first point in degrees

  • +
  • a12 – spherical arc length from the first point to the second +in degrees

  • +
  • outmask – the output mask

  • +
+
+
Returns:
+

a dict

+
+
+

Compute geodesic starting at (lat1, lon1) with azimuth azi1 +and arc length a12. The default value of outmask is STANDARD, +i.e., the lat1, lon1, azi1, lat2, lon2, azi2, s12, +a12 entries are returned.

+
+ +
+
+ArcDirectLine(lat1, lon1, azi1, a12, caps=3979)[source]
+

Define a GeodesicLine object in terms of the direct geodesic +problem specified in terms of spherical arc length

+
+
Parameters:
+
    +
  • lat1 – latitude of the first point in degrees

  • +
  • lon1 – longitude of the first point in degrees

  • +
  • azi1 – azimuth at the first point in degrees

  • +
  • a12 – spherical arc length from the first point to the second +in degrees

  • +
  • caps – the capabilities

  • +
+
+
Returns:
+

a GeodesicLine

+
+
+

This function sets point 3 of the GeodesicLine to correspond to +point 2 of the direct geodesic problem. The default value of caps +is STANDARD | DISTANCE_IN, allowing direct geodesic problem to be +solved.

+
+ +
+
+Direct(lat1, lon1, azi1, s12, outmask=1929)[source]
+

Solve the direct geodesic problem

+
+
Parameters:
+
    +
  • lat1 – latitude of the first point in degrees

  • +
  • lon1 – longitude of the first point in degrees

  • +
  • azi1 – azimuth at the first point in degrees

  • +
  • s12 – the distance from the first point to the second in +meters

  • +
  • outmask – the output mask

  • +
+
+
Returns:
+

a dict

+
+
+

Compute geodesic starting at (lat1, lon1) with azimuth azi1 +and length s12. The default value of outmask is STANDARD, i.e., +the lat1, lon1, azi1, lat2, lon2, azi2, s12, a12 +entries are returned.

+
+ +
+
+DirectLine(lat1, lon1, azi1, s12, caps=3979)[source]
+

Define a GeodesicLine object in terms of the direct geodesic +problem specified in terms of spherical arc length

+
+
Parameters:
+
    +
  • lat1 – latitude of the first point in degrees

  • +
  • lon1 – longitude of the first point in degrees

  • +
  • azi1 – azimuth at the first point in degrees

  • +
  • s12 – the distance from the first point to the second in +meters

  • +
  • caps – the capabilities

  • +
+
+
Returns:
+

a GeodesicLine

+
+
+

This function sets point 3 of the GeodesicLine to correspond to +point 2 of the direct geodesic problem. The default value of caps +is STANDARD | DISTANCE_IN, allowing direct geodesic problem to be +solved.

+
+ +
+
+Inverse(lat1, lon1, lat2, lon2, outmask=1929)[source]
+

Solve the inverse geodesic problem

+
+
Parameters:
+
    +
  • lat1 – latitude of the first point in degrees

  • +
  • lon1 – longitude of the first point in degrees

  • +
  • lat2 – latitude of the second point in degrees

  • +
  • lon2 – longitude of the second point in degrees

  • +
  • outmask – the output mask

  • +
+
+
Returns:
+

a dict

+
+
+

Compute geodesic between (lat1, lon1) and (lat2, lon2). +The default value of outmask is STANDARD, i.e., the lat1, +lon1, azi1, lat2, lon2, azi2, s12, a12 entries are +returned.

+
+ +
+
+InverseLine(lat1, lon1, lat2, lon2, caps=3979)[source]
+

Define a GeodesicLine object in terms of the invese geodesic problem

+
+
Parameters:
+
    +
  • lat1 – latitude of the first point in degrees

  • +
  • lon1 – longitude of the first point in degrees

  • +
  • lat2 – latitude of the second point in degrees

  • +
  • lon2 – longitude of the second point in degrees

  • +
  • caps – the capabilities

  • +
+
+
Returns:
+

a GeodesicLine

+
+
+

This function sets point 3 of the GeodesicLine to correspond to +point 2 of the inverse geodesic problem. The default value of caps +is STANDARD | DISTANCE_IN, allowing direct geodesic problem to be +solved.

+
+ +
+
+Line(lat1, lon1, azi1, caps=3979)[source]
+

Return a GeodesicLine object

+
+
Parameters:
+
    +
  • lat1 – latitude of the first point in degrees

  • +
  • lon1 – longitude of the first point in degrees

  • +
  • azi1 – azimuth at the first point in degrees

  • +
  • caps – the capabilities

  • +
+
+
Returns:
+

a GeodesicLine

+
+
+

This allows points along a geodesic starting at (lat1, lon1), +with azimuth azi1 to be found. The default value of caps is +STANDARD | DISTANCE_IN, allowing direct geodesic problem to be +solved.

+
+ +
+
+Polygon(polyline=False)[source]
+

Return a PolygonArea object

+
+
Parameters:
+

polyline – if True then the object describes a polyline +instead of a polygon

+
+
Returns:
+

a PolygonArea

+
+
+
+ +
+
+ALL = 32671
+

All of the above.

+
+ +
+
+AREA = 16400
+

Calculate area S12.

+
+ +
+
+AZIMUTH = 512
+

Calculate azimuths azi1 and azi2.

+
+ +
+
+CAP_ALL = 31
+
+ +
+
+CAP_C1 = 1
+
+ +
+
+CAP_C1p = 2
+
+ +
+
+CAP_C2 = 4
+
+ +
+
+CAP_C3 = 8
+
+ +
+
+CAP_C4 = 16
+
+ +
+
+CAP_MASK = 31
+
+ +
+
+CAP_NONE = 0
+
+ +
+
+DISTANCE = 1025
+

Calculate distance s12.

+
+ +
+
+DISTANCE_IN = 2051
+

Allow distance s12 to be used as input in the direct geodesic +problem.

+
+ +
+
+EMPTY = 0
+

No capabilities, no output.

+
+ +
+
+GEODESICSCALE = 8197
+

Calculate geodesic scales M12 and M21.

+
+ +
+
+GEOGRAPHICLIB_GEODESIC_ORDER = 6
+
+ +
+
+LATITUDE = 128
+

Calculate latitude lat2.

+
+ +
+
+LONGITUDE = 264
+

Calculate longitude lon2.

+
+ +
+
+LONG_UNROLL = 32768
+

Unroll longitudes, rather than reducing them to the range +[-180d,180d].

+
+ +
+
+OUT_ALL = 32640
+
+ +
+
+OUT_MASK = 65408
+
+ +
+
+REDUCEDLENGTH = 4101
+

Calculate reduced length m12.

+
+ +
+
+STANDARD = 1929
+

All of the above.

+
+ +
+
+WGS84 = <geographiclib.geodesic.Geodesic object>
+
+ +
+
+a
+

The equatorial radius in meters (readonly)

+
+ +
+
+f
+

The flattening (readonly)

+
+ +
+
+maxit1_ = 20
+
+ +
+
+maxit2_ = 83
+
+ +
+
+nA1_ = 6
+
+ +
+
+nA2_ = 6
+
+ +
+
+nA3_ = 6
+
+ +
+
+nA3x_ = 6
+
+ +
+
+nC1_ = 6
+
+ +
+
+nC1p_ = 6
+
+ +
+
+nC2_ = 6
+
+ +
+
+nC3_ = 6
+
+ +
+
+nC3x_ = 15
+
+ +
+
+nC4_ = 6
+
+ +
+
+nC4x_ = 21
+
+ +
+
+tiny_ = 1.4916681462400413e-154
+
+ +
+
+tol0_ = 2.220446049250313e-16
+
+ +
+
+tol1_ = 4.440892098500626e-14
+
+ +
+
+tol2_ = 1.4901161193847656e-08
+
+ +
+
+tolb_ = 3.308722450212111e-24
+
+ +
+
+xthresh_ = 1.4901161193847656e-05
+
+ +
+ +
+
+class network_wrangler.utils.LineString(coordinates=None)[source]
+

Bases: BaseGeometry

+

A geometry type composed of one or more line segments.

+

A LineString is a one-dimensional feature and has a non-zero length but +zero area. It may approximate a curve and need not be straight. Unlike a +LinearRing, a LineString is not closed.

+
+
Parameters:
+

coordinates (sequence) – A sequence of (x, y, [,z]) numeric coordinate pairs or triples, or +an array-like with shape (N, 2) or (N, 3). +Also can be a sequence of Point objects.

+
+
+

Examples

+

Create a LineString with two segments

+
>>> a = LineString([[0, 0], [1, 0], [1, 1]])
+>>> a.length
+2.0
+
+
+
+
+almost_equals(other, decimal=6)
+

True if geometries are equal at all coordinates to a +specified decimal place.

+
+

Deprecated since version 1.8.0: The ‘almost_equals()’ method is deprecated +and will be removed in Shapely 2.1 because the name is +confusing. The ‘equals_exact()’ method should be used +instead.

+
+

Refers to approximate coordinate equality, which requires +coordinates to be approximately equal and in the same order for +all components of a geometry.

+

Because of this it is possible for “equals()” to be True for two +geometries and “almost_equals()” to be False.

+

Examples

+
>>> LineString(
+...     [(0, 0), (2, 2)]
+... ).equals_exact(
+...     LineString([(0, 0), (1, 1), (2, 2)]),
+...     1e-6
+... )
+False
+
+
+
+
Return type:
+

bool

+
+
+
+ +
+
+buffer(distance, quad_segs=16, cap_style='round', join_style='round', mitre_limit=5.0, single_sided=False, **kwargs)
+

Get a geometry that represents all points within a distance +of this geometry.

+

A positive distance produces a dilation, a negative distance an +erosion. A very small or zero distance may sometimes be used to +“tidy” a polygon.

+
+
Parameters:
+
    +
  • distance (float) – The distance to buffer around the object.

  • +
  • resolution (int, optional) – The resolution of the buffer around each vertex of the +object.

  • +
  • quad_segs (int, optional) – Sets the number of line segments used to approximate an +angle fillet.

  • +
  • cap_style (shapely.BufferCapStyle or {'round', 'square', 'flat'}, default 'round') – Specifies the shape of buffered line endings. BufferCapStyle.round (‘round’) +results in circular line endings (see quad_segs). Both BufferCapStyle.square +(‘square’) and BufferCapStyle.flat (‘flat’) result in rectangular line endings, +only BufferCapStyle.flat (‘flat’) will end at the original vertex, +while BufferCapStyle.square (‘square’) involves adding the buffer width.

  • +
  • join_style (shapely.BufferJoinStyle or {'round', 'mitre', 'bevel'}, default 'round') – Specifies the shape of buffered line midpoints. BufferJoinStyle.ROUND (‘round’) +results in rounded shapes. BufferJoinStyle.bevel (‘bevel’) results in a beveled +edge that touches the original vertex. BufferJoinStyle.mitre (‘mitre’) results +in a single vertex that is beveled depending on the mitre_limit parameter.

  • +
  • mitre_limit (float, optional) – The mitre limit ratio is used for very sharp corners. The +mitre ratio is the ratio of the distance from the corner to +the end of the mitred offset corner. When two line segments +meet at a sharp angle, a miter join will extend the original +geometry. To prevent unreasonable geometry, the mitre limit +allows controlling the maximum length of the join corner. +Corners with a ratio which exceed the limit will be beveled.

  • +
  • single_side (bool, optional) –

    The side used is determined by the sign of the buffer +distance:

    +
    +

    a positive distance indicates the left-hand side +a negative distance indicates the right-hand side

    +
    +

    The single-sided buffer of point geometries is the same as +the regular buffer. The End Cap Style for single-sided +buffers is always ignored, and forced to the equivalent of +CAP_FLAT.

    +

  • +
  • quadsegs (int, optional) – Deprecated alias for quad_segs.

  • +
+
+
Return type:
+

Geometry

+
+
+

Notes

+

The return value is a strictly two-dimensional geometry. All +Z coordinates of the original geometry will be ignored.

+

Examples

+
>>> from shapely.wkt import loads
+>>> g = loads('POINT (0.0 0.0)')
+
+
+

16-gon approx of a unit radius circle:

+
>>> g.buffer(1.0).area  
+3.1365484905459...
+
+
+

128-gon approximation:

+
>>> g.buffer(1.0, 128).area  
+3.141513801144...
+
+
+

triangle approximation:

+
>>> g.buffer(1.0, 3).area
+3.0
+>>> list(g.buffer(1.0, cap_style=BufferCapStyle.square).exterior.coords)
+[(1.0, 1.0), (1.0, -1.0), (-1.0, -1.0), (-1.0, 1.0), (1.0, 1.0)]
+>>> g.buffer(1.0, cap_style=BufferCapStyle.square).area
+4.0
+
+
+
+ +
+
+contains(other)
+

Returns True if the geometry contains the other, else False

+
+ +
+
+contains_properly(other)
+

Returns True if the geometry completely contains the other, with no +common boundary points, else False

+

Refer to shapely.contains_properly for full documentation.

+
+ +
+
+covered_by(other)
+

Returns True if the geometry is covered by the other, else False

+
+ +
+
+covers(other)
+

Returns True if the geometry covers the other, else False

+
+ +
+
+crosses(other)
+

Returns True if the geometries cross, else False

+
+ +
+
+difference(other, grid_size=None)
+

Returns the difference of the geometries.

+

Refer to shapely.difference for full documentation.

+
+ +
+
+disjoint(other)
+

Returns True if geometries are disjoint, else False

+
+ +
+
+distance(other)
+

Unitless distance to other geometry (float)

+
+ +
+
+dwithin(other, distance)
+

Returns True if geometry is within a given distance from the other, else False.

+

Refer to shapely.dwithin for full documentation.

+
+ +
+
+equals(other)
+

Returns True if geometries are equal, else False.

+

This method considers point-set equality (or topological +equality), and is equivalent to (self.within(other) & +self.contains(other)).

+

Examples

+
>>> LineString(
+...     [(0, 0), (2, 2)]
+... ).equals(
+...     LineString([(0, 0), (1, 1), (2, 2)])
+... )
+True
+
+
+
+
Return type:
+

bool

+
+
+
+ +
+
+equals_exact(other, tolerance)
+

True if geometries are equal to within a specified +tolerance.

+
+
Parameters:
+
    +
  • other (BaseGeometry) – The other geometry object in this comparison.

  • +
  • tolerance (float) – Absolute tolerance in the same units as coordinates.

  • +
  • equality (This method considers coordinate) –

  • +
  • requires (which) –

  • +
  • components (coordinates to be equal and in the same order for all) –

  • +
  • geometry. (of a) –

  • +
  • two (Because of this it is possible for "equals()" to be True for) –

  • +
  • False. (geometries and "equals_exact()" to be) –

  • +
+
+
+

Examples

+
>>> LineString(
+...     [(0, 0), (2, 2)]
+... ).equals_exact(
+...     LineString([(0, 0), (1, 1), (2, 2)]),
+...     1e-6
+... )
+False
+
+
+
+
Return type:
+

bool

+
+
+
+ +
+
+geometryType()
+
+ +
+
+hausdorff_distance(other)
+

Unitless hausdorff distance to other geometry (float)

+
+ +
+
+interpolate(distance, normalized=False)
+

Return a point at the specified distance along a linear geometry

+

Negative length values are taken as measured in the reverse +direction from the end of the geometry. Out-of-range index +values are handled by clamping them to the valid range of values. +If the normalized arg is True, the distance will be interpreted as a +fraction of the geometry’s length.

+

Alias of line_interpolate_point.

+
+ +
+
+intersection(other, grid_size=None)
+

Returns the intersection of the geometries.

+

Refer to shapely.intersection for full documentation.

+
+ +
+
+intersects(other)
+

Returns True if geometries intersect, else False

+
+ +
+
+line_interpolate_point(distance, normalized=False)
+

Return a point at the specified distance along a linear geometry

+

Negative length values are taken as measured in the reverse +direction from the end of the geometry. Out-of-range index +values are handled by clamping them to the valid range of values. +If the normalized arg is True, the distance will be interpreted as a +fraction of the geometry’s length.

+

Alias of interpolate.

+
+ +
+
+line_locate_point(other, normalized=False)
+

Returns the distance along this geometry to a point nearest the +specified point

+

If the normalized arg is True, return the distance normalized to the +length of the linear geometry.

+

Alias of project.

+
+ +
+
+normalize()
+

Converts geometry to normal form (or canonical form).

+

This method orders the coordinates, rings of a polygon and parts of +multi geometries consistently. Typically useful for testing purposes +(for example in combination with equals_exact).

+

Examples

+
>>> from shapely import MultiLineString
+>>> line = MultiLineString([[(0, 0), (1, 1)], [(3, 3), (2, 2)]])
+>>> line.normalize()
+<MULTILINESTRING ((2 2, 3 3), (0 0, 1 1))>
+
+
+
+ +
+
+offset_curve(distance, quad_segs=16, join_style=BufferJoinStyle.round, mitre_limit=5.0)[source]
+

Returns a LineString or MultiLineString geometry at a distance from +the object on its right or its left side.

+

The side is determined by the sign of the distance parameter +(negative for right side offset, positive for left side offset). The +resolution of the buffer around each vertex of the object increases +by increasing the quad_segs keyword parameter.

+

The join style is for outside corners between line segments. Accepted +values are JOIN_STYLE.round (1), JOIN_STYLE.mitre (2), and +JOIN_STYLE.bevel (3).

+

The mitre ratio limit is used for very sharp corners. It is the ratio +of the distance from the corner to the end of the mitred offset corner. +When two line segments meet at a sharp angle, a miter join will extend +far beyond the original geometry. To prevent unreasonable geometry, the +mitre limit allows controlling the maximum length of the join corner. +Corners with a ratio which exceed the limit will be beveled.

+

Note: the behaviour regarding orientation of the resulting line +depends on the GEOS version. With GEOS < 3.11, the line retains the +same direction for a left offset (positive distance) or has reverse +direction for a right offset (negative distance), and this behaviour +was documented as such in previous Shapely versions. Starting with +GEOS 3.11, the function tries to preserve the orientation of the +original line.

+
+ +
+
+overlaps(other)
+

Returns True if geometries overlap, else False

+
+ +
+
+parallel_offset(distance, side='right', resolution=16, join_style=BufferJoinStyle.round, mitre_limit=5.0)[source]
+

Alternative method to offset_curve() method.

+

Older alternative method to the offset_curve() method, but uses +resolution instead of quad_segs and a side keyword +(‘left’ or ‘right’) instead of sign of the distance. This method is +kept for backwards compatibility for now, but is is recommended to +use offset_curve() instead.

+
+ +
+
+point_on_surface()
+

Returns a point guaranteed to be within the object, cheaply.

+

Alias of representative_point.

+
+ +
+
+project(other, normalized=False)
+

Returns the distance along this geometry to a point nearest the +specified point

+

If the normalized arg is True, return the distance normalized to the +length of the linear geometry.

+

Alias of line_locate_point.

+
+ +
+
+relate(other)
+

Returns the DE-9IM intersection matrix for the two geometries +(string)

+
+ +
+
+relate_pattern(other, pattern)
+

Returns True if the DE-9IM string code for the relationship between +the geometries satisfies the pattern, else False

+
+ +
+
+representative_point()
+

Returns a point guaranteed to be within the object, cheaply.

+

Alias of point_on_surface.

+
+ +
+
+reverse()
+

Returns a copy of this geometry with the order of coordinates reversed.

+

If the geometry is a polygon with interior rings, the interior rings are also +reversed.

+

Points are unchanged.

+
+

See also

+
+
is_ccw

Checks if a geometry is clockwise.

+
+
+
+

Examples

+
>>> from shapely import LineString, Polygon
+>>> LineString([(0, 0), (1, 2)]).reverse()
+<LINESTRING (1 2, 0 0)>
+>>> Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]).reverse()
+<POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))>
+
+
+
+ +
+
+segmentize(max_segment_length)
+

Adds vertices to line segments based on maximum segment length.

+

Additional vertices will be added to every line segment in an input geometry +so that segments are no longer than the provided maximum segment length. New +vertices will evenly subdivide each segment.

+

Only linear components of input geometries are densified; other geometries +are returned unmodified.

+
+
Parameters:
+

max_segment_length (float or array_like) – Additional vertices will be added so that all line segments are no +longer this value. Must be greater than 0.

+
+
+

Examples

+
>>> from shapely import LineString, Polygon
+>>> LineString([(0, 0), (0, 10)]).segmentize(max_segment_length=5)
+<LINESTRING (0 0, 0 5, 0 10)>
+>>> Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]).segmentize(max_segment_length=5)
+<POLYGON ((0 0, 5 0, 10 0, 10 5, 10 10, 5 10, 0 10, 0 5, 0 0))>
+
+
+
+ +
+
+simplify(tolerance, preserve_topology=True)
+

Returns a simplified geometry produced by the Douglas-Peucker +algorithm

+

Coordinates of the simplified geometry will be no more than the +tolerance distance from the original. Unless the topology preserving +option is used, the algorithm may produce self-intersecting or +otherwise invalid geometries.

+
+ +
+
+svg(scale_factor=1.0, stroke_color=None, opacity=None)[source]
+

Returns SVG polyline element for the LineString geometry.

+
+
Parameters:
+
    +
  • scale_factor (float) – Multiplication factor for the SVG stroke-width. Default is 1.

  • +
  • stroke_color (str, optional) – Hex string for stroke color. Default is to use “#66cc99” if +geometry is valid, and “#ff3333” if invalid.

  • +
  • opacity (float) – Float number between 0 and 1 for color opacity. Default value is 0.8

  • +
+
+
+
+ +
+
+symmetric_difference(other, grid_size=None)
+

Returns the symmetric difference of the geometries.

+

Refer to shapely.symmetric_difference for full documentation.

+
+ +
+
+touches(other)
+

Returns True if geometries touch, else False

+
+ +
+
+union(other, grid_size=None)
+

Returns the union of the geometries.

+

Refer to shapely.union for full documentation.

+
+ +
+
+within(other)
+

Returns True if geometry is within the other, else False

+
+ +
+
+property area
+

Unitless area of the geometry (float)

+
+ +
+
+property boundary
+

Returns a lower dimension geometry that bounds the object

+

The boundary of a polygon is a line, the boundary of a line is a +collection of points. The boundary of a point is an empty (null) +collection.

+
+ +
+
+property bounds
+

Returns minimum bounding region (minx, miny, maxx, maxy)

+
+ +
+
+property centroid
+

Returns the geometric center of the object

+
+ +
+
+property convex_hull
+

that’s a +convex hull, more or less

+

The convex hull of a three member multipoint, for example, is a +triangular polygon.

+
+
Type:
+

Imagine an elastic band stretched around the geometry

+
+
+
+ +
+
+property coords
+

Access to geometry’s coordinates (CoordinateSequence)

+
+ +
+
+property envelope
+

A figure that envelopes the geometry

+
+ +
+
+property geom_type
+

Name of the geometry’s type, such as ‘Point’

+
+ +
+
+property has_z
+

True if the geometry’s coordinate sequence(s) have z values (are +3-dimensional)

+
+ +
+
+property is_closed
+

True if the geometry is closed, else False

+

Applicable only to 1-D geometries.

+
+ +
+
+property is_empty
+

True if the set of points in this geometry is empty, else False

+
+ +
+
+property is_ring
+

True if the geometry is a closed ring, else False

+
+ +
+
+property is_simple
+

True if the geometry is simple, meaning that any self-intersections +are only at boundary points, else False

+
+ +
+
+property is_valid
+

True if the geometry is valid (definition depends on sub-class), +else False

+
+ +
+
+property length
+

Unitless length of the geometry (float)

+
+ +
+
+property minimum_clearance
+

Unitless distance by which a node could be moved to produce an invalid geometry (float)

+
+ +
+
+property minimum_rotated_rectangle
+

Returns the oriented envelope (minimum rotated rectangle) that +encloses the geometry.

+

Unlike envelope this rectangle is not constrained to be parallel to the +coordinate axes. If the convex hull of the object is a degenerate (line +or point) this degenerate is returned.

+

Alias of oriented_envelope.

+
+ +
+
+property oriented_envelope
+

Returns the oriented envelope (minimum rotated rectangle) that +encloses the geometry.

+

Unlike envelope this rectangle is not constrained to be parallel to the +coordinate axes. If the convex hull of the object is a degenerate (line +or point) this degenerate is returned.

+

Alias of minimum_rotated_rectangle.

+
+ +
+
+property type
+
+ +
+
+property wkb
+

WKB representation of the geometry

+
+ +
+
+property wkb_hex
+

WKB hex representation of the geometry

+
+ +
+
+property wkt
+

WKT representation of the geometry

+
+ +
+
+property xy
+

Separate arrays of X and Y coordinate values

+

Example

+
>>> x, y = LineString([(0, 0), (1, 1)]).xy
+>>> list(x)
+[0.0, 1.0]
+>>> list(y)
+[0.0, 1.0]
+
+
+
+ +
+ +
+
+network_wrangler.utils.create_line_string(location_reference)[source]
+

Creates a geometry as a LineString using location reference

+
+ +
+
+network_wrangler.utils.create_location_reference_from_nodes(node_a, node_b)[source]
+

Creates a location reference using the node a and node b coordinates

+

Args: +node_a: Node A as Series +node_b: Node B as Series

+
+ +
+
+network_wrangler.utils.create_unique_shape_id(line_string)[source]
+

Creates a unique hash id using the coordinates of the geomtery

+

Args: +line_string: Line Geometry as a LineString

+

Returns: string

+
+ +
+
+network_wrangler.utils.get_bearing(lat1, lon1, lat2, lon2)[source]
+

calculate the bearing (forward azimuth) b/w the two points

+

returns: bearing in radians

+
+ +
+
+network_wrangler.utils.haversine_distance(origin, destination, units='miles')[source]
+

Calculates haversine distance between two points

+

Args: +origin: lat/lon for point A +destination: lat/lon for point B +units: either “miles” or “meters”

+

Returns: string

+
+ +
+ +

Export pandas dataframe as a json object.

+

Modified from: Geoff Boeing: +https://geoffboeing.com/2015/10/exporting-python-data-geojson/

+
+
Parameters:
+
    +
  • df – Dataframe to export

  • +
  • properties – list of properties to export

  • +
+
+
+
+ +
+
+network_wrangler.utils.make_slug(text, delimiter='_')[source]
+

makes a slug from text

+
+ +
+
+network_wrangler.utils.offset_location_reference(location_reference, offset_meters=10)[source]
+

Creates a new location reference +using the node a and node b of given location reference, +offseting it by 90 degree to the bearing of given location reference +and distance equals to offset_meters

+

returns: new location_reference with offset

+
+ +
+
+network_wrangler.utils.offset_point_with_distance_and_bearing(lat, lon, distance, bearing)[source]
+

Get the new lat long (in degrees) given current point (lat/lon), distance and bearing

+

returns: new lat/long

+
+ +
+
+network_wrangler.utils.parse_time_spans(times)[source]
+

parse time spans into tuples of seconds from midnight +can also be used as an apply function for a pandas series +:param times: +:type times: tuple(string) or tuple(int) or list(string) or list(int)

+
+
Returns:
+

time span as seconds from midnight

+
+
Return type:
+

tuple(integer)

+
+
+
+ +
+
+network_wrangler.utils.point_df_to_geojson(df, properties, node_foreign_key=None)[source]
+

Author: Geoff Boeing: +https://geoffboeing.com/2015/10/exporting-python-data-geojson/

+
+ +
+
+network_wrangler.utils.topological_sort(adjacency_list, visited_list)[source]
+

Topological sorting for Acyclic Directed Graph

+
+ +
+
+network_wrangler.utils.update_df(base_df, update_df, merge_key=None, left_on=None, right_on=None, update_fields=None, method='update if found')[source]
+

Updates specific fields of a dataframe with another dataframe using a key column.

+
+
Parameters:
+
    +
  • base_df – DataFrame to be updated

  • +
  • update_df – DataFrame with with updated values

  • +
  • merge_key – column to merge on (i.e. model_link_id). If not specified, must have left_on AND right_on.

  • +
  • left_on – key for base_df. Must also specify right_on. If not specified, must specify merge_key.

  • +
  • right_on – key for update_df. Must also specify left_on. If not specified, must specify merge_key.

  • +
  • update_fields – required list of fields to update values for. Must be columns in update_df.

  • +
  • method – string indicating how the dataframe should be updated. One of: +- “update if found” (default) which will update the values if the update values are not NaN +- “overwrite all” will overwrite the current value with the update value even if it is NaN +- “update nan” will only update values that are currently Nan in the base_df

  • +
+
+
+

Returns: Dataframe with updated values

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/geographiclib/geodesic/index.html b/_modules/geographiclib/geodesic/index.html new file mode 100644 index 00000000..5c920b5e --- /dev/null +++ b/_modules/geographiclib/geodesic/index.html @@ -0,0 +1,1396 @@ + + + + + + geographiclib.geodesic — Network Wrangler documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for geographiclib.geodesic

+"""Define the :class:`~geographiclib.geodesic.Geodesic` class
+
+The ellipsoid parameters are defined by the constructor.  The direct and
+inverse geodesic problems are solved by
+
+  * :meth:`~geographiclib.geodesic.Geodesic.Inverse` Solve the inverse
+    geodesic problem
+  * :meth:`~geographiclib.geodesic.Geodesic.Direct` Solve the direct
+    geodesic problem
+  * :meth:`~geographiclib.geodesic.Geodesic.ArcDirect` Solve the direct
+    geodesic problem in terms of spherical arc length
+
+:class:`~geographiclib.geodesicline.GeodesicLine` objects can be created
+with
+
+  * :meth:`~geographiclib.geodesic.Geodesic.Line`
+  * :meth:`~geographiclib.geodesic.Geodesic.DirectLine`
+  * :meth:`~geographiclib.geodesic.Geodesic.ArcDirectLine`
+  * :meth:`~geographiclib.geodesic.Geodesic.InverseLine`
+
+:class:`~geographiclib.polygonarea.PolygonArea` objects can be created
+with
+
+  * :meth:`~geographiclib.geodesic.Geodesic.Polygon`
+
+The public attributes for this class are
+
+  * :attr:`~geographiclib.geodesic.Geodesic.a`
+    :attr:`~geographiclib.geodesic.Geodesic.f`
+
+*outmask* and *caps* bit masks are
+
+  * :const:`~geographiclib.geodesic.Geodesic.EMPTY`
+  * :const:`~geographiclib.geodesic.Geodesic.LATITUDE`
+  * :const:`~geographiclib.geodesic.Geodesic.LONGITUDE`
+  * :const:`~geographiclib.geodesic.Geodesic.AZIMUTH`
+  * :const:`~geographiclib.geodesic.Geodesic.DISTANCE`
+  * :const:`~geographiclib.geodesic.Geodesic.STANDARD`
+  * :const:`~geographiclib.geodesic.Geodesic.DISTANCE_IN`
+  * :const:`~geographiclib.geodesic.Geodesic.REDUCEDLENGTH`
+  * :const:`~geographiclib.geodesic.Geodesic.GEODESICSCALE`
+  * :const:`~geographiclib.geodesic.Geodesic.AREA`
+  * :const:`~geographiclib.geodesic.Geodesic.ALL`
+  * :const:`~geographiclib.geodesic.Geodesic.LONG_UNROLL`
+
+:Example:
+
+    >>> from geographiclib.geodesic import Geodesic
+    >>> # The geodesic inverse problem
+    ... Geodesic.WGS84.Inverse(-41.32, 174.81, 40.96, -5.50)
+    {'lat1': -41.32,
+     'a12': 179.6197069334283,
+     's12': 19959679.26735382,
+     'lat2': 40.96,
+     'azi2': 18.825195123248392,
+     'azi1': 161.06766998615882,
+     'lon1': 174.81,
+     'lon2': -5.5}
+
+"""
+# geodesic.py
+#
+# This is a rather literal translation of the GeographicLib::Geodesic class to
+# python.  See the documentation for the C++ class for more information at
+#
+#    https://geographiclib.sourceforge.io/html/annotated.html
+#
+# The algorithms are derived in
+#
+#    Charles F. F. Karney,
+#    Algorithms for geodesics, J. Geodesy 87, 43-55 (2013),
+#    https://doi.org/10.1007/s00190-012-0578-z
+#    Addenda: https://geographiclib.sourceforge.io/geod-addenda.html
+#
+# Copyright (c) Charles Karney (2011-2022) <charles@karney.com> and licensed
+# under the MIT/X11 License.  For more information, see
+# https://geographiclib.sourceforge.io/
+######################################################################
+
+import math
+import sys
+from geographiclib.geomath import Math
+from geographiclib.constants import Constants
+from geographiclib.geodesiccapability import GeodesicCapability
+
+
[docs]class Geodesic: + """Solve geodesic problems""" + + GEOGRAPHICLIB_GEODESIC_ORDER = 6 + nA1_ = GEOGRAPHICLIB_GEODESIC_ORDER + nC1_ = GEOGRAPHICLIB_GEODESIC_ORDER + nC1p_ = GEOGRAPHICLIB_GEODESIC_ORDER + nA2_ = GEOGRAPHICLIB_GEODESIC_ORDER + nC2_ = GEOGRAPHICLIB_GEODESIC_ORDER + nA3_ = GEOGRAPHICLIB_GEODESIC_ORDER + nA3x_ = nA3_ + nC3_ = GEOGRAPHICLIB_GEODESIC_ORDER + nC3x_ = (nC3_ * (nC3_ - 1)) // 2 + nC4_ = GEOGRAPHICLIB_GEODESIC_ORDER + nC4x_ = (nC4_ * (nC4_ + 1)) // 2 + maxit1_ = 20 + maxit2_ = maxit1_ + sys.float_info.mant_dig + 10 + + tiny_ = math.sqrt(sys.float_info.min) + tol0_ = sys.float_info.epsilon + tol1_ = 200 * tol0_ + tol2_ = math.sqrt(tol0_) + tolb_ = tol0_ * tol2_ + xthresh_ = 1000 * tol2_ + + CAP_NONE = GeodesicCapability.CAP_NONE + CAP_C1 = GeodesicCapability.CAP_C1 + CAP_C1p = GeodesicCapability.CAP_C1p + CAP_C2 = GeodesicCapability.CAP_C2 + CAP_C3 = GeodesicCapability.CAP_C3 + CAP_C4 = GeodesicCapability.CAP_C4 + CAP_ALL = GeodesicCapability.CAP_ALL + CAP_MASK = GeodesicCapability.CAP_MASK + OUT_ALL = GeodesicCapability.OUT_ALL + OUT_MASK = GeodesicCapability.OUT_MASK + + @staticmethod + def _SinCosSeries(sinp, sinx, cosx, c): + """Private: Evaluate a trig series using Clenshaw summation.""" + # Evaluate + # y = sinp ? sum(c[i] * sin( 2*i * x), i, 1, n) : + # sum(c[i] * cos((2*i+1) * x), i, 0, n-1) + # using Clenshaw summation. N.B. c[0] is unused for sin series + # Approx operation count = (n + 5) mult and (2 * n + 2) add + k = len(c) # Point to one beyond last element + n = k - sinp + ar = 2 * (cosx - sinx) * (cosx + sinx) # 2 * cos(2 * x) + y1 = 0 # accumulators for sum + if n & 1: + k -= 1; y0 = c[k] + else: + y0 = 0 + # Now n is even + n = n // 2 + while n: # while n--: + n -= 1 + # Unroll loop x 2, so accumulators return to their original role + k -= 1; y1 = ar * y0 - y1 + c[k] + k -= 1; y0 = ar * y1 - y0 + c[k] + return ( 2 * sinx * cosx * y0 if sinp # sin(2 * x) * y0 + else cosx * (y0 - y1) ) # cos(x) * (y0 - y1) + + @staticmethod + def _Astroid(x, y): + """Private: solve astroid equation.""" + # Solve k^4+2*k^3-(x^2+y^2-1)*k^2-2*y^2*k-y^2 = 0 for positive root k. + # This solution is adapted from Geocentric::Reverse. + p = Math.sq(x) + q = Math.sq(y) + r = (p + q - 1) / 6 + if not(q == 0 and r <= 0): + # Avoid possible division by zero when r = 0 by multiplying equations + # for s and t by r^3 and r, resp. + S = p * q / 4 # S = r^3 * s + r2 = Math.sq(r) + r3 = r * r2 + # The discriminant of the quadratic equation for T3. This is zero on + # the evolute curve p^(1/3)+q^(1/3) = 1 + disc = S * (S + 2 * r3) + u = r + if disc >= 0: + T3 = S + r3 + # Pick the sign on the sqrt to maximize abs(T3). This minimizes loss + # of precision due to cancellation. The result is unchanged because + # of the way the T is used in definition of u. + T3 += -math.sqrt(disc) if T3 < 0 else math.sqrt(disc) # T3 = (r * t)^3 + # N.B. cbrt always returns the real root. cbrt(-8) = -2. + T = Math.cbrt(T3) # T = r * t + # T can be zero; but then r2 / T -> 0. + u += T + (r2 / T if T != 0 else 0) + else: + # T is complex, but the way u is defined the result is real. + ang = math.atan2(math.sqrt(-disc), -(S + r3)) + # There are three possible cube roots. We choose the root which + # avoids cancellation. Note that disc < 0 implies that r < 0. + u += 2 * r * math.cos(ang / 3) + v = math.sqrt(Math.sq(u) + q) # guaranteed positive + # Avoid loss of accuracy when u < 0. + uv = q / (v - u) if u < 0 else u + v # u+v, guaranteed positive + w = (uv - q) / (2 * v) # positive? + # Rearrange expression for k to avoid loss of accuracy due to + # subtraction. Division by 0 not possible because uv > 0, w >= 0. + k = uv / (math.sqrt(uv + Math.sq(w)) + w) # guaranteed positive + else: # q == 0 && r <= 0 + # y = 0 with |x| <= 1. Handle this case directly. + # for y small, positive root is k = abs(y)/sqrt(1-x^2) + k = 0 + return k + + @staticmethod + def _A1m1f(eps): + """Private: return A1-1.""" + coeff = [ + 1, 4, 64, 0, 256, + ] + m = Geodesic.nA1_//2 + t = Math.polyval(m, coeff, 0, Math.sq(eps)) / coeff[m + 1] + return (t + eps) / (1 - eps) + + @staticmethod + def _C1f(eps, c): + """Private: return C1.""" + coeff = [ + -1, 6, -16, 32, + -9, 64, -128, 2048, + 9, -16, 768, + 3, -5, 512, + -7, 1280, + -7, 2048, + ] + eps2 = Math.sq(eps) + d = eps + o = 0 + for l in range(1, Geodesic.nC1_ + 1): # l is index of C1p[l] + m = (Geodesic.nC1_ - l) // 2 # order of polynomial in eps^2 + c[l] = d * Math.polyval(m, coeff, o, eps2) / coeff[o + m + 1] + o += m + 2 + d *= eps + + @staticmethod + def _C1pf(eps, c): + """Private: return C1'""" + coeff = [ + 205, -432, 768, 1536, + 4005, -4736, 3840, 12288, + -225, 116, 384, + -7173, 2695, 7680, + 3467, 7680, + 38081, 61440, + ] + eps2 = Math.sq(eps) + d = eps + o = 0 + for l in range(1, Geodesic.nC1p_ + 1): # l is index of C1p[l] + m = (Geodesic.nC1p_ - l) // 2 # order of polynomial in eps^2 + c[l] = d * Math.polyval(m, coeff, o, eps2) / coeff[o + m + 1] + o += m + 2 + d *= eps + + @staticmethod + def _A2m1f(eps): + """Private: return A2-1""" + coeff = [ + -11, -28, -192, 0, 256, + ] + m = Geodesic.nA2_//2 + t = Math.polyval(m, coeff, 0, Math.sq(eps)) / coeff[m + 1] + return (t - eps) / (1 + eps) + + @staticmethod + def _C2f(eps, c): + """Private: return C2""" + coeff = [ + 1, 2, 16, 32, + 35, 64, 384, 2048, + 15, 80, 768, + 7, 35, 512, + 63, 1280, + 77, 2048, + ] + eps2 = Math.sq(eps) + d = eps + o = 0 + for l in range(1, Geodesic.nC2_ + 1): # l is index of C2[l] + m = (Geodesic.nC2_ - l) // 2 # order of polynomial in eps^2 + c[l] = d * Math.polyval(m, coeff, o, eps2) / coeff[o + m + 1] + o += m + 2 + d *= eps + + def __init__(self, a, f): + """Construct a Geodesic object + + :param a: the equatorial radius of the ellipsoid in meters + :param f: the flattening of the ellipsoid + + An exception is thrown if *a* or the polar semi-axis *b* = *a* (1 - + *f*) is not a finite positive quantity. + + """ + + self.a = float(a) + """The equatorial radius in meters (readonly)""" + self.f = float(f) + """The flattening (readonly)""" + self._f1 = 1 - self.f + self._e2 = self.f * (2 - self.f) + self._ep2 = self._e2 / Math.sq(self._f1) # e2 / (1 - e2) + self._n = self.f / ( 2 - self.f) + self._b = self.a * self._f1 + # authalic radius squared + self._c2 = (Math.sq(self.a) + Math.sq(self._b) * + (1 if self._e2 == 0 else + (math.atanh(math.sqrt(self._e2)) if self._e2 > 0 else + math.atan(math.sqrt(-self._e2))) / + math.sqrt(abs(self._e2))))/2 + # The sig12 threshold for "really short". Using the auxiliary sphere + # solution with dnm computed at (bet1 + bet2) / 2, the relative error in + # the azimuth consistency check is sig12^2 * abs(f) * min(1, 1-f/2) / 2. + # (Error measured for 1/100 < b/a < 100 and abs(f) >= 1/1000. For a given + # f and sig12, the max error occurs for lines near the pole. If the old + # rule for computing dnm = (dn1 + dn2)/2 is used, then the error increases + # by a factor of 2.) Setting this equal to epsilon gives sig12 = etol2. + # Here 0.1 is a safety factor (error decreased by 100) and max(0.001, + # abs(f)) stops etol2 getting too large in the nearly spherical case. + self._etol2 = 0.1 * Geodesic.tol2_ / math.sqrt( max(0.001, abs(self.f)) * + min(1.0, 1-self.f/2) / 2 ) + if not(math.isfinite(self.a) and self.a > 0): + raise ValueError("Equatorial radius is not positive") + if not(math.isfinite(self._b) and self._b > 0): + raise ValueError("Polar semi-axis is not positive") + self._A3x = list(range(Geodesic.nA3x_)) + self._C3x = list(range(Geodesic.nC3x_)) + self._C4x = list(range(Geodesic.nC4x_)) + self._A3coeff() + self._C3coeff() + self._C4coeff() + + def _A3coeff(self): + """Private: return coefficients for A3""" + coeff = [ + -3, 128, + -2, -3, 64, + -1, -3, -1, 16, + 3, -1, -2, 8, + 1, -1, 2, + 1, 1, + ] + o = 0; k = 0 + for j in range(Geodesic.nA3_ - 1, -1, -1): # coeff of eps^j + m = min(Geodesic.nA3_ - j - 1, j) # order of polynomial in n + self._A3x[k] = Math.polyval(m, coeff, o, self._n) / coeff[o + m + 1] + k += 1 + o += m + 2 + + def _C3coeff(self): + """Private: return coefficients for C3""" + coeff = [ + 3, 128, + 2, 5, 128, + -1, 3, 3, 64, + -1, 0, 1, 8, + -1, 1, 4, + 5, 256, + 1, 3, 128, + -3, -2, 3, 64, + 1, -3, 2, 32, + 7, 512, + -10, 9, 384, + 5, -9, 5, 192, + 7, 512, + -14, 7, 512, + 21, 2560, + ] + o = 0; k = 0 + for l in range(1, Geodesic.nC3_): # l is index of C3[l] + for j in range(Geodesic.nC3_ - 1, l - 1, -1): # coeff of eps^j + m = min(Geodesic.nC3_ - j - 1, j) # order of polynomial in n + self._C3x[k] = Math.polyval(m, coeff, o, self._n) / coeff[o + m + 1] + k += 1 + o += m + 2 + + def _C4coeff(self): + """Private: return coefficients for C4""" + coeff = [ + 97, 15015, + 1088, 156, 45045, + -224, -4784, 1573, 45045, + -10656, 14144, -4576, -858, 45045, + 64, 624, -4576, 6864, -3003, 15015, + 100, 208, 572, 3432, -12012, 30030, 45045, + 1, 9009, + -2944, 468, 135135, + 5792, 1040, -1287, 135135, + 5952, -11648, 9152, -2574, 135135, + -64, -624, 4576, -6864, 3003, 135135, + 8, 10725, + 1856, -936, 225225, + -8448, 4992, -1144, 225225, + -1440, 4160, -4576, 1716, 225225, + -136, 63063, + 1024, -208, 105105, + 3584, -3328, 1144, 315315, + -128, 135135, + -2560, 832, 405405, + 128, 99099, + ] + o = 0; k = 0 + for l in range(Geodesic.nC4_): # l is index of C4[l] + for j in range(Geodesic.nC4_ - 1, l - 1, -1): # coeff of eps^j + m = Geodesic.nC4_ - j - 1 # order of polynomial in n + self._C4x[k] = Math.polyval(m, coeff, o, self._n) / coeff[o + m + 1] + k += 1 + o += m + 2 + + def _A3f(self, eps): + """Private: return A3""" + # Evaluate A3 + return Math.polyval(Geodesic.nA3_ - 1, self._A3x, 0, eps) + + def _C3f(self, eps, c): + """Private: return C3""" + # Evaluate C3 + # Elements c[1] thru c[nC3_ - 1] are set + mult = 1 + o = 0 + for l in range(1, Geodesic.nC3_): # l is index of C3[l] + m = Geodesic.nC3_ - l - 1 # order of polynomial in eps + mult *= eps + c[l] = mult * Math.polyval(m, self._C3x, o, eps) + o += m + 1 + + def _C4f(self, eps, c): + """Private: return C4""" + # Evaluate C4 coeffs by Horner's method + # Elements c[0] thru c[nC4_ - 1] are set + mult = 1 + o = 0 + for l in range(Geodesic.nC4_): # l is index of C4[l] + m = Geodesic.nC4_ - l - 1 # order of polynomial in eps + c[l] = mult * Math.polyval(m, self._C4x, o, eps) + o += m + 1 + mult *= eps + + # return s12b, m12b, m0, M12, M21 + def _Lengths(self, eps, sig12, + ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1, cbet2, outmask, + # Scratch areas of the right size + C1a, C2a): + """Private: return a bunch of lengths""" + # Return s12b, m12b, m0, M12, M21, where + # m12b = (reduced length)/_b; s12b = distance/_b, + # m0 = coefficient of secular term in expression for reduced length. + outmask &= Geodesic.OUT_MASK + # outmask & DISTANCE: set s12b + # outmask & REDUCEDLENGTH: set m12b & m0 + # outmask & GEODESICSCALE: set M12 & M21 + + s12b = m12b = m0 = M12 = M21 = math.nan + if outmask & (Geodesic.DISTANCE | Geodesic.REDUCEDLENGTH | + Geodesic.GEODESICSCALE): + A1 = Geodesic._A1m1f(eps) + Geodesic._C1f(eps, C1a) + if outmask & (Geodesic.REDUCEDLENGTH | Geodesic.GEODESICSCALE): + A2 = Geodesic._A2m1f(eps) + Geodesic._C2f(eps, C2a) + m0x = A1 - A2 + A2 = 1 + A2 + A1 = 1 + A1 + if outmask & Geodesic.DISTANCE: + B1 = (Geodesic._SinCosSeries(True, ssig2, csig2, C1a) - + Geodesic._SinCosSeries(True, ssig1, csig1, C1a)) + # Missing a factor of _b + s12b = A1 * (sig12 + B1) + if outmask & (Geodesic.REDUCEDLENGTH | Geodesic.GEODESICSCALE): + B2 = (Geodesic._SinCosSeries(True, ssig2, csig2, C2a) - + Geodesic._SinCosSeries(True, ssig1, csig1, C2a)) + J12 = m0x * sig12 + (A1 * B1 - A2 * B2) + elif outmask & (Geodesic.REDUCEDLENGTH | Geodesic.GEODESICSCALE): + # Assume here that nC1_ >= nC2_ + for l in range(1, Geodesic.nC2_): + C2a[l] = A1 * C1a[l] - A2 * C2a[l] + J12 = m0x * sig12 + (Geodesic._SinCosSeries(True, ssig2, csig2, C2a) - + Geodesic._SinCosSeries(True, ssig1, csig1, C2a)) + if outmask & Geodesic.REDUCEDLENGTH: + m0 = m0x + # Missing a factor of _b. + # Add parens around (csig1 * ssig2) and (ssig1 * csig2) to ensure + # accurate cancellation in the case of coincident points. + m12b = (dn2 * (csig1 * ssig2) - dn1 * (ssig1 * csig2) - + csig1 * csig2 * J12) + if outmask & Geodesic.GEODESICSCALE: + csig12 = csig1 * csig2 + ssig1 * ssig2 + t = self._ep2 * (cbet1 - cbet2) * (cbet1 + cbet2) / (dn1 + dn2) + M12 = csig12 + (t * ssig2 - csig2 * J12) * ssig1 / dn1 + M21 = csig12 - (t * ssig1 - csig1 * J12) * ssig2 / dn2 + return s12b, m12b, m0, M12, M21 + + # return sig12, salp1, calp1, salp2, calp2, dnm + def _InverseStart(self, sbet1, cbet1, dn1, sbet2, cbet2, dn2, + lam12, slam12, clam12, + # Scratch areas of the right size + C1a, C2a): + """Private: Find a starting value for Newton's method.""" + # Return a starting point for Newton's method in salp1 and calp1 (function + # value is -1). If Newton's method doesn't need to be used, return also + # salp2 and calp2 and function value is sig12. + sig12 = -1; salp2 = calp2 = dnm = math.nan # Return values + # bet12 = bet2 - bet1 in [0, pi); bet12a = bet2 + bet1 in (-pi, 0] + sbet12 = sbet2 * cbet1 - cbet2 * sbet1 + cbet12 = cbet2 * cbet1 + sbet2 * sbet1 + # Volatile declaration needed to fix inverse cases + # 88.202499451857 0 -88.202499451857 179.981022032992859592 + # 89.262080389218 0 -89.262080389218 179.992207982775375662 + # 89.333123580033 0 -89.333123580032997687 179.99295812360148422 + # which otherwise fail with g++ 4.4.4 x86 -O3 + sbet12a = sbet2 * cbet1 + sbet12a += cbet2 * sbet1 + + shortline = cbet12 >= 0 and sbet12 < 0.5 and cbet2 * lam12 < 0.5 + if shortline: + sbetm2 = Math.sq(sbet1 + sbet2) + # sin((bet1+bet2)/2)^2 + # = (sbet1 + sbet2)^2 / ((sbet1 + sbet2)^2 + (cbet1 + cbet2)^2) + sbetm2 /= sbetm2 + Math.sq(cbet1 + cbet2) + dnm = math.sqrt(1 + self._ep2 * sbetm2) + omg12 = lam12 / (self._f1 * dnm) + somg12 = math.sin(omg12); comg12 = math.cos(omg12) + else: + somg12 = slam12; comg12 = clam12 + + salp1 = cbet2 * somg12 + calp1 = ( + sbet12 + cbet2 * sbet1 * Math.sq(somg12) / (1 + comg12) if comg12 >= 0 + else sbet12a - cbet2 * sbet1 * Math.sq(somg12) / (1 - comg12)) + + ssig12 = math.hypot(salp1, calp1) + csig12 = sbet1 * sbet2 + cbet1 * cbet2 * comg12 + + if shortline and ssig12 < self._etol2: + # really short lines + salp2 = cbet1 * somg12 + calp2 = sbet12 - cbet1 * sbet2 * (Math.sq(somg12) / (1 + comg12) + if comg12 >= 0 else 1 - comg12) + salp2, calp2 = Math.norm(salp2, calp2) + # Set return value + sig12 = math.atan2(ssig12, csig12) + elif (abs(self._n) >= 0.1 or # Skip astroid calc if too eccentric + csig12 >= 0 or + ssig12 >= 6 * abs(self._n) * math.pi * Math.sq(cbet1)): + # Nothing to do, zeroth order spherical approximation is OK + pass + else: + # Scale lam12 and bet2 to x, y coordinate system where antipodal point + # is at origin and singular point is at y = 0, x = -1. + # real y, lamscale, betscale + lam12x = math.atan2(-slam12, -clam12) + if self.f >= 0: # In fact f == 0 does not get here + # x = dlong, y = dlat + k2 = Math.sq(sbet1) * self._ep2 + eps = k2 / (2 * (1 + math.sqrt(1 + k2)) + k2) + lamscale = self.f * cbet1 * self._A3f(eps) * math.pi + betscale = lamscale * cbet1 + x = lam12x / lamscale + y = sbet12a / betscale + else: # _f < 0 + # x = dlat, y = dlong + cbet12a = cbet2 * cbet1 - sbet2 * sbet1 + bet12a = math.atan2(sbet12a, cbet12a) + # real m12b, m0, dummy + # In the case of lon12 = 180, this repeats a calculation made in + # Inverse. + dummy, m12b, m0, dummy, dummy = self._Lengths( + self._n, math.pi + bet12a, sbet1, -cbet1, dn1, sbet2, cbet2, dn2, + cbet1, cbet2, Geodesic.REDUCEDLENGTH, C1a, C2a) + x = -1 + m12b / (cbet1 * cbet2 * m0 * math.pi) + betscale = (sbet12a / x if x < -0.01 + else -self.f * Math.sq(cbet1) * math.pi) + lamscale = betscale / cbet1 + y = lam12x / lamscale + + if y > -Geodesic.tol1_ and x > -1 - Geodesic.xthresh_: + # strip near cut + if self.f >= 0: + salp1 = min(1.0, -x); calp1 = - math.sqrt(1 - Math.sq(salp1)) + else: + calp1 = max((0.0 if x > -Geodesic.tol1_ else -1.0), x) + salp1 = math.sqrt(1 - Math.sq(calp1)) + else: + # Estimate alp1, by solving the astroid problem. + # + # Could estimate alpha1 = theta + pi/2, directly, i.e., + # calp1 = y/k; salp1 = -x/(1+k); for _f >= 0 + # calp1 = x/(1+k); salp1 = -y/k; for _f < 0 (need to check) + # + # However, it's better to estimate omg12 from astroid and use + # spherical formula to compute alp1. This reduces the mean number of + # Newton iterations for astroid cases from 2.24 (min 0, max 6) to 2.12 + # (min 0 max 5). The changes in the number of iterations are as + # follows: + # + # change percent + # 1 5 + # 0 78 + # -1 16 + # -2 0.6 + # -3 0.04 + # -4 0.002 + # + # The histogram of iterations is (m = number of iterations estimating + # alp1 directly, n = number of iterations estimating via omg12, total + # number of trials = 148605): + # + # iter m n + # 0 148 186 + # 1 13046 13845 + # 2 93315 102225 + # 3 36189 32341 + # 4 5396 7 + # 5 455 1 + # 6 56 0 + # + # Because omg12 is near pi, estimate work with omg12a = pi - omg12 + k = Geodesic._Astroid(x, y) + omg12a = lamscale * ( -x * k/(1 + k) if self.f >= 0 + else -y * (1 + k)/k ) + somg12 = math.sin(omg12a); comg12 = -math.cos(omg12a) + # Update spherical estimate of alp1 using omg12 instead of lam12 + salp1 = cbet2 * somg12 + calp1 = sbet12a - cbet2 * sbet1 * Math.sq(somg12) / (1 - comg12) + # Sanity check on starting guess. Backwards check allows NaN through. + if not (salp1 <= 0): + salp1, calp1 = Math.norm(salp1, calp1) + else: + salp1 = 1; calp1 = 0 + return sig12, salp1, calp1, salp2, calp2, dnm + + # return lam12, salp2, calp2, sig12, ssig1, csig1, ssig2, csig2, eps, + # domg12, dlam12 + def _Lambda12(self, sbet1, cbet1, dn1, sbet2, cbet2, dn2, salp1, calp1, + slam120, clam120, diffp, + # Scratch areas of the right size + C1a, C2a, C3a): + """Private: Solve hybrid problem""" + if sbet1 == 0 and calp1 == 0: + # Break degeneracy of equatorial line. This case has already been + # handled. + calp1 = -Geodesic.tiny_ + + # sin(alp1) * cos(bet1) = sin(alp0) + salp0 = salp1 * cbet1 + calp0 = math.hypot(calp1, salp1 * sbet1) # calp0 > 0 + + # real somg1, comg1, somg2, comg2, lam12 + # tan(bet1) = tan(sig1) * cos(alp1) + # tan(omg1) = sin(alp0) * tan(sig1) = tan(omg1)=tan(alp1)*sin(bet1) + ssig1 = sbet1; somg1 = salp0 * sbet1 + csig1 = comg1 = calp1 * cbet1 + ssig1, csig1 = Math.norm(ssig1, csig1) + # Math.norm(somg1, comg1); -- don't need to normalize! + + # Enforce symmetries in the case abs(bet2) = -bet1. Need to be careful + # about this case, since this can yield singularities in the Newton + # iteration. + # sin(alp2) * cos(bet2) = sin(alp0) + salp2 = salp0 / cbet2 if cbet2 != cbet1 else salp1 + # calp2 = sqrt(1 - sq(salp2)) + # = sqrt(sq(calp0) - sq(sbet2)) / cbet2 + # and subst for calp0 and rearrange to give (choose positive sqrt + # to give alp2 in [0, pi/2]). + calp2 = (math.sqrt(Math.sq(calp1 * cbet1) + + ((cbet2 - cbet1) * (cbet1 + cbet2) if cbet1 < -sbet1 + else (sbet1 - sbet2) * (sbet1 + sbet2))) / cbet2 + if cbet2 != cbet1 or abs(sbet2) != -sbet1 else abs(calp1)) + # tan(bet2) = tan(sig2) * cos(alp2) + # tan(omg2) = sin(alp0) * tan(sig2). + ssig2 = sbet2; somg2 = salp0 * sbet2 + csig2 = comg2 = calp2 * cbet2 + ssig2, csig2 = Math.norm(ssig2, csig2) + # Math.norm(somg2, comg2); -- don't need to normalize! + + # sig12 = sig2 - sig1, limit to [0, pi] + sig12 = math.atan2(max(0.0, csig1 * ssig2 - ssig1 * csig2) + 0.0, + csig1 * csig2 + ssig1 * ssig2) + + # omg12 = omg2 - omg1, limit to [0, pi] + somg12 = max(0.0, comg1 * somg2 - somg1 * comg2) + 0.0 + comg12 = comg1 * comg2 + somg1 * somg2 + # eta = omg12 - lam120 + eta = math.atan2(somg12 * clam120 - comg12 * slam120, + comg12 * clam120 + somg12 * slam120) + + # real B312 + k2 = Math.sq(calp0) * self._ep2 + eps = k2 / (2 * (1 + math.sqrt(1 + k2)) + k2) + self._C3f(eps, C3a) + B312 = (Geodesic._SinCosSeries(True, ssig2, csig2, C3a) - + Geodesic._SinCosSeries(True, ssig1, csig1, C3a)) + domg12 = -self.f * self._A3f(eps) * salp0 * (sig12 + B312) + lam12 = eta + domg12 + + if diffp: + if calp2 == 0: + dlam12 = - 2 * self._f1 * dn1 / sbet1 + else: + dummy, dlam12, dummy, dummy, dummy = self._Lengths( + eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1, cbet2, + Geodesic.REDUCEDLENGTH, C1a, C2a) + dlam12 *= self._f1 / (calp2 * cbet2) + else: + dlam12 = math.nan + + return (lam12, salp2, calp2, sig12, ssig1, csig1, ssig2, csig2, eps, + domg12, dlam12) + + # return a12, s12, salp1, calp1, salp2, calp2, m12, M12, M21, S12 + def _GenInverse(self, lat1, lon1, lat2, lon2, outmask): + """Private: General version of the inverse problem""" + a12 = s12 = m12 = M12 = M21 = S12 = math.nan # return vals + + outmask &= Geodesic.OUT_MASK + # Compute longitude difference (AngDiff does this carefully). Result is + # in [-180, 180] but -180 is only for west-going geodesics. 180 is for + # east-going and meridional geodesics. + lon12, lon12s = Math.AngDiff(lon1, lon2) + # Make longitude difference positive. + lonsign = math.copysign(1, lon12) + lon12 = lonsign * lon12; lon12s = lonsign * lon12s + lam12 = math.radians(lon12) + # Calculate sincos of lon12 + error (this applies AngRound internally). + slam12, clam12 = Math.sincosde(lon12, lon12s) + lon12s = (180 - lon12) - lon12s # the supplementary longitude difference + + # If really close to the equator, treat as on equator. + lat1 = Math.AngRound(Math.LatFix(lat1)) + lat2 = Math.AngRound(Math.LatFix(lat2)) + # Swap points so that point with higher (abs) latitude is point 1 + # If one latitude is a nan, then it becomes lat1. + swapp = -1 if abs(lat1) < abs(lat2) or math.isnan(lat2) else 1 + if swapp < 0: + lonsign *= -1 + lat2, lat1 = lat1, lat2 + # Make lat1 <= 0 + latsign = math.copysign(1, -lat1) + lat1 *= latsign + lat2 *= latsign + # Now we have + # + # 0 <= lon12 <= 180 + # -90 <= lat1 <= 0 + # lat1 <= lat2 <= -lat1 + # + # longsign, swapp, latsign register the transformation to bring the + # coordinates to this canonical form. In all cases, 1 means no change was + # made. We make these transformations so that there are few cases to + # check, e.g., on verifying quadrants in atan2. In addition, this + # enforces some symmetries in the results returned. + + # real phi, sbet1, cbet1, sbet2, cbet2, s12x, m12x + + sbet1, cbet1 = Math.sincosd(lat1); sbet1 *= self._f1 + # Ensure cbet1 = +epsilon at poles + sbet1, cbet1 = Math.norm(sbet1, cbet1); cbet1 = max(Geodesic.tiny_, cbet1) + + sbet2, cbet2 = Math.sincosd(lat2); sbet2 *= self._f1 + # Ensure cbet2 = +epsilon at poles + sbet2, cbet2 = Math.norm(sbet2, cbet2); cbet2 = max(Geodesic.tiny_, cbet2) + + # If cbet1 < -sbet1, then cbet2 - cbet1 is a sensitive measure of the + # |bet1| - |bet2|. Alternatively (cbet1 >= -sbet1), abs(sbet2) + sbet1 is + # a better measure. This logic is used in assigning calp2 in Lambda12. + # Sometimes these quantities vanish and in that case we force bet2 = +/- + # bet1 exactly. An example where is is necessary is the inverse problem + # 48.522876735459 0 -48.52287673545898293 179.599720456223079643 + # which failed with Visual Studio 10 (Release and Debug) + + if cbet1 < -sbet1: + if cbet2 == cbet1: + sbet2 = math.copysign(sbet1, sbet2) + else: + if abs(sbet2) == -sbet1: + cbet2 = cbet1 + + dn1 = math.sqrt(1 + self._ep2 * Math.sq(sbet1)) + dn2 = math.sqrt(1 + self._ep2 * Math.sq(sbet2)) + + # real a12, sig12, calp1, salp1, calp2, salp2 + # index zero elements of these arrays are unused + C1a = list(range(Geodesic.nC1_ + 1)) + C2a = list(range(Geodesic.nC2_ + 1)) + C3a = list(range(Geodesic.nC3_)) + + meridian = lat1 == -90 or slam12 == 0 + + if meridian: + + # Endpoints are on a single full meridian, so the geodesic might lie on + # a meridian. + + calp1 = clam12; salp1 = slam12 # Head to the target longitude + calp2 = 1.0; salp2 = 0.0 # At the target we're heading north + + # tan(bet) = tan(sig) * cos(alp) + ssig1 = sbet1; csig1 = calp1 * cbet1 + ssig2 = sbet2; csig2 = calp2 * cbet2 + + # sig12 = sig2 - sig1 + sig12 = math.atan2(max(0.0, csig1 * ssig2 - ssig1 * csig2) + 0.0, + csig1 * csig2 + ssig1 * ssig2) + + s12x, m12x, dummy, M12, M21 = self._Lengths( + self._n, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1, cbet2, + outmask | Geodesic.DISTANCE | Geodesic.REDUCEDLENGTH, C1a, C2a) + + # Add the check for sig12 since zero length geodesics might yield m12 < + # 0. Test case was + # + # echo 20.001 0 20.001 0 | GeodSolve -i + # + # In fact, we will have sig12 > pi/2 for meridional geodesic which is + # not a shortest path. + if sig12 < 1 or m12x >= 0: + if (sig12 < 3 * Geodesic.tiny_ or + # Prevent negative s12 or m12 for short lines + (sig12 < Geodesic.tol0_ and (s12x < 0 or m12x < 0))): + sig12 = m12x = s12x = 0.0 + m12x *= self._b + s12x *= self._b + a12 = math.degrees(sig12) + else: + # m12 < 0, i.e., prolate and too close to anti-podal + meridian = False + # end if meridian: + + # somg12 == 2 marks that it needs to be calculated + somg12 = 2.0; comg12 = 0.0; omg12 = 0.0 + if (not meridian and + sbet1 == 0 and # and sbet2 == 0 + # Mimic the way Lambda12 works with calp1 = 0 + (self.f <= 0 or lon12s >= self.f * 180)): + + # Geodesic runs along equator + calp1 = calp2 = 0.0; salp1 = salp2 = 1.0 + s12x = self.a * lam12 + sig12 = omg12 = lam12 / self._f1 + m12x = self._b * math.sin(sig12) + if outmask & Geodesic.GEODESICSCALE: + M12 = M21 = math.cos(sig12) + a12 = lon12 / self._f1 + + elif not meridian: + + # Now point1 and point2 belong within a hemisphere bounded by a + # meridian and geodesic is neither meridional or equatorial. + + # Figure a starting point for Newton's method + sig12, salp1, calp1, salp2, calp2, dnm = self._InverseStart( + sbet1, cbet1, dn1, sbet2, cbet2, dn2, lam12, slam12, clam12, C1a, C2a) + + if sig12 >= 0: + # Short lines (InverseStart sets salp2, calp2, dnm) + s12x = sig12 * self._b * dnm + m12x = (Math.sq(dnm) * self._b * math.sin(sig12 / dnm)) + if outmask & Geodesic.GEODESICSCALE: + M12 = M21 = math.cos(sig12 / dnm) + a12 = math.degrees(sig12) + omg12 = lam12 / (self._f1 * dnm) + else: + + # Newton's method. This is a straightforward solution of f(alp1) = + # lambda12(alp1) - lam12 = 0 with one wrinkle. f(alp) has exactly one + # root in the interval (0, pi) and its derivative is positive at the + # root. Thus f(alp) is positive for alp > alp1 and negative for alp < + # alp1. During the course of the iteration, a range (alp1a, alp1b) is + # maintained which brackets the root and with each evaluation of f(alp) + # the range is shrunk if possible. Newton's method is restarted + # whenever the derivative of f is negative (because the new value of + # alp1 is then further from the solution) or if the new estimate of + # alp1 lies outside (0,pi); in this case, the new starting guess is + # taken to be (alp1a + alp1b) / 2. + # real ssig1, csig1, ssig2, csig2, eps + numit = 0 + tripn = tripb = False + # Bracketing range + salp1a = Geodesic.tiny_; calp1a = 1.0 + salp1b = Geodesic.tiny_; calp1b = -1.0 + + while numit < Geodesic.maxit2_: + # the WGS84 test set: mean = 1.47, sd = 1.25, max = 16 + # WGS84 and random input: mean = 2.85, sd = 0.60 + (v, salp2, calp2, sig12, ssig1, csig1, ssig2, csig2, + eps, domg12, dv) = self._Lambda12( + sbet1, cbet1, dn1, sbet2, cbet2, dn2, + salp1, calp1, slam12, clam12, numit < Geodesic.maxit1_, + C1a, C2a, C3a) + # Reversed test to allow escape with NaNs + if tripb or not (abs(v) >= (8 if tripn else 1) * Geodesic.tol0_): + break + # Update bracketing values + if v > 0 and (numit > Geodesic.maxit1_ or + calp1/salp1 > calp1b/salp1b): + salp1b = salp1; calp1b = calp1 + elif v < 0 and (numit > Geodesic.maxit1_ or + calp1/salp1 < calp1a/salp1a): + salp1a = salp1; calp1a = calp1 + + numit += 1 + if numit < Geodesic.maxit1_ and dv > 0: + dalp1 = -v/dv + sdalp1 = math.sin(dalp1); cdalp1 = math.cos(dalp1) + nsalp1 = salp1 * cdalp1 + calp1 * sdalp1 + if nsalp1 > 0 and abs(dalp1) < math.pi: + calp1 = calp1 * cdalp1 - salp1 * sdalp1 + salp1 = nsalp1 + salp1, calp1 = Math.norm(salp1, calp1) + # In some regimes we don't get quadratic convergence because + # slope -> 0. So use convergence conditions based on epsilon + # instead of sqrt(epsilon). + tripn = abs(v) <= 16 * Geodesic.tol0_ + continue + # Either dv was not positive or updated value was outside + # legal range. Use the midpoint of the bracket as the next + # estimate. This mechanism is not needed for the WGS84 + # ellipsoid, but it does catch problems with more eccentric + # ellipsoids. Its efficacy is such for + # the WGS84 test set with the starting guess set to alp1 = 90deg: + # the WGS84 test set: mean = 5.21, sd = 3.93, max = 24 + # WGS84 and random input: mean = 4.74, sd = 0.99 + salp1 = (salp1a + salp1b)/2 + calp1 = (calp1a + calp1b)/2 + salp1, calp1 = Math.norm(salp1, calp1) + tripn = False + tripb = (abs(salp1a - salp1) + (calp1a - calp1) < Geodesic.tolb_ or + abs(salp1 - salp1b) + (calp1 - calp1b) < Geodesic.tolb_) + + lengthmask = (outmask | + (Geodesic.DISTANCE + if (outmask & (Geodesic.REDUCEDLENGTH | + Geodesic.GEODESICSCALE)) + else Geodesic.EMPTY)) + s12x, m12x, dummy, M12, M21 = self._Lengths( + eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1, cbet2, + lengthmask, C1a, C2a) + + m12x *= self._b + s12x *= self._b + a12 = math.degrees(sig12) + if outmask & Geodesic.AREA: + # omg12 = lam12 - domg12 + sdomg12 = math.sin(domg12); cdomg12 = math.cos(domg12) + somg12 = slam12 * cdomg12 - clam12 * sdomg12 + comg12 = clam12 * cdomg12 + slam12 * sdomg12 + + # end elif not meridian + + if outmask & Geodesic.DISTANCE: + s12 = 0.0 + s12x # Convert -0 to 0 + + if outmask & Geodesic.REDUCEDLENGTH: + m12 = 0.0 + m12x # Convert -0 to 0 + + if outmask & Geodesic.AREA: + # From Lambda12: sin(alp1) * cos(bet1) = sin(alp0) + salp0 = salp1 * cbet1 + calp0 = math.hypot(calp1, salp1 * sbet1) # calp0 > 0 + # real alp12 + if calp0 != 0 and salp0 != 0: + # From Lambda12: tan(bet) = tan(sig) * cos(alp) + ssig1 = sbet1; csig1 = calp1 * cbet1 + ssig2 = sbet2; csig2 = calp2 * cbet2 + k2 = Math.sq(calp0) * self._ep2 + eps = k2 / (2 * (1 + math.sqrt(1 + k2)) + k2) + # Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0). + A4 = Math.sq(self.a) * calp0 * salp0 * self._e2 + ssig1, csig1 = Math.norm(ssig1, csig1) + ssig2, csig2 = Math.norm(ssig2, csig2) + C4a = list(range(Geodesic.nC4_)) + self._C4f(eps, C4a) + B41 = Geodesic._SinCosSeries(False, ssig1, csig1, C4a) + B42 = Geodesic._SinCosSeries(False, ssig2, csig2, C4a) + S12 = A4 * (B42 - B41) + else: + # Avoid problems with indeterminate sig1, sig2 on equator + S12 = 0.0 + + if not meridian and somg12 == 2.0: + somg12 = math.sin(omg12); comg12 = math.cos(omg12) + + if (not meridian and + # omg12 < 3/4 * pi + comg12 > -0.7071 and # Long difference not too big + sbet2 - sbet1 < 1.75): # Lat difference not too big + # Use tan(Gamma/2) = tan(omg12/2) + # * (tan(bet1/2)+tan(bet2/2))/(1+tan(bet1/2)*tan(bet2/2)) + # with tan(x/2) = sin(x)/(1+cos(x)) + domg12 = 1 + comg12; dbet1 = 1 + cbet1; dbet2 = 1 + cbet2 + alp12 = 2 * math.atan2( somg12 * ( sbet1 * dbet2 + sbet2 * dbet1 ), + domg12 * ( sbet1 * sbet2 + dbet1 * dbet2 ) ) + else: + # alp12 = alp2 - alp1, used in atan2 so no need to normalize + salp12 = salp2 * calp1 - calp2 * salp1 + calp12 = calp2 * calp1 + salp2 * salp1 + # The right thing appears to happen if alp1 = +/-180 and alp2 = 0, viz + # salp12 = -0 and alp12 = -180. However this depends on the sign + # being attached to 0 correctly. The following ensures the correct + # behavior. + if salp12 == 0 and calp12 < 0: + salp12 = Geodesic.tiny_ * calp1 + calp12 = -1.0 + alp12 = math.atan2(salp12, calp12) + S12 += self._c2 * alp12 + S12 *= swapp * lonsign * latsign + # Convert -0 to 0 + S12 += 0.0 + + # Convert calp, salp to azimuth accounting for lonsign, swapp, latsign. + if swapp < 0: + salp2, salp1 = salp1, salp2 + calp2, calp1 = calp1, calp2 + if outmask & Geodesic.GEODESICSCALE: + M21, M12 = M12, M21 + + salp1 *= swapp * lonsign; calp1 *= swapp * latsign + salp2 *= swapp * lonsign; calp2 *= swapp * latsign + + return a12, s12, salp1, calp1, salp2, calp2, m12, M12, M21, S12 + +
[docs] def Inverse(self, lat1, lon1, lat2, lon2, + outmask = GeodesicCapability.STANDARD): + """Solve the inverse geodesic problem + + :param lat1: latitude of the first point in degrees + :param lon1: longitude of the first point in degrees + :param lat2: latitude of the second point in degrees + :param lon2: longitude of the second point in degrees + :param outmask: the :ref:`output mask <outmask>` + :return: a :ref:`dict` + + Compute geodesic between (*lat1*, *lon1*) and (*lat2*, *lon2*). + The default value of *outmask* is STANDARD, i.e., the *lat1*, + *lon1*, *azi1*, *lat2*, *lon2*, *azi2*, *s12*, *a12* entries are + returned. + + """ + + a12, s12, salp1,calp1, salp2,calp2, m12, M12, M21, S12 = self._GenInverse( + lat1, lon1, lat2, lon2, outmask) + outmask &= Geodesic.OUT_MASK + if outmask & Geodesic.LONG_UNROLL: + lon12, e = Math.AngDiff(lon1, lon2) + lon2 = (lon1 + lon12) + e + else: + lon2 = Math.AngNormalize(lon2) + result = {'lat1': Math.LatFix(lat1), + 'lon1': lon1 if outmask & Geodesic.LONG_UNROLL else + Math.AngNormalize(lon1), + 'lat2': Math.LatFix(lat2), + 'lon2': lon2} + result['a12'] = a12 + if outmask & Geodesic.DISTANCE: result['s12'] = s12 + if outmask & Geodesic.AZIMUTH: + result['azi1'] = Math.atan2d(salp1, calp1) + result['azi2'] = Math.atan2d(salp2, calp2) + if outmask & Geodesic.REDUCEDLENGTH: result['m12'] = m12 + if outmask & Geodesic.GEODESICSCALE: + result['M12'] = M12; result['M21'] = M21 + if outmask & Geodesic.AREA: result['S12'] = S12 + return result
+ + # return a12, lat2, lon2, azi2, s12, m12, M12, M21, S12 + def _GenDirect(self, lat1, lon1, azi1, arcmode, s12_a12, outmask): + """Private: General version of direct problem""" + from geographiclib.geodesicline import GeodesicLine + # Automatically supply DISTANCE_IN if necessary + if not arcmode: outmask |= Geodesic.DISTANCE_IN + line = GeodesicLine(self, lat1, lon1, azi1, outmask) + return line._GenPosition(arcmode, s12_a12, outmask) + +
[docs] def Direct(self, lat1, lon1, azi1, s12, + outmask = GeodesicCapability.STANDARD): + """Solve the direct geodesic problem + + :param lat1: latitude of the first point in degrees + :param lon1: longitude of the first point in degrees + :param azi1: azimuth at the first point in degrees + :param s12: the distance from the first point to the second in + meters + :param outmask: the :ref:`output mask <outmask>` + :return: a :ref:`dict` + + Compute geodesic starting at (*lat1*, *lon1*) with azimuth *azi1* + and length *s12*. The default value of *outmask* is STANDARD, i.e., + the *lat1*, *lon1*, *azi1*, *lat2*, *lon2*, *azi2*, *s12*, *a12* + entries are returned. + + """ + + a12, lat2, lon2, azi2, s12, m12, M12, M21, S12 = self._GenDirect( + lat1, lon1, azi1, False, s12, outmask) + outmask &= Geodesic.OUT_MASK + result = {'lat1': Math.LatFix(lat1), + 'lon1': lon1 if outmask & Geodesic.LONG_UNROLL else + Math.AngNormalize(lon1), + 'azi1': Math.AngNormalize(azi1), + 's12': s12} + result['a12'] = a12 + if outmask & Geodesic.LATITUDE: result['lat2'] = lat2 + if outmask & Geodesic.LONGITUDE: result['lon2'] = lon2 + if outmask & Geodesic.AZIMUTH: result['azi2'] = azi2 + if outmask & Geodesic.REDUCEDLENGTH: result['m12'] = m12 + if outmask & Geodesic.GEODESICSCALE: + result['M12'] = M12; result['M21'] = M21 + if outmask & Geodesic.AREA: result['S12'] = S12 + return result
+ +
[docs] def ArcDirect(self, lat1, lon1, azi1, a12, + outmask = GeodesicCapability.STANDARD): + """Solve the direct geodesic problem in terms of spherical arc length + + :param lat1: latitude of the first point in degrees + :param lon1: longitude of the first point in degrees + :param azi1: azimuth at the first point in degrees + :param a12: spherical arc length from the first point to the second + in degrees + :param outmask: the :ref:`output mask <outmask>` + :return: a :ref:`dict` + + Compute geodesic starting at (*lat1*, *lon1*) with azimuth *azi1* + and arc length *a12*. The default value of *outmask* is STANDARD, + i.e., the *lat1*, *lon1*, *azi1*, *lat2*, *lon2*, *azi2*, *s12*, + *a12* entries are returned. + + """ + + a12, lat2, lon2, azi2, s12, m12, M12, M21, S12 = self._GenDirect( + lat1, lon1, azi1, True, a12, outmask) + outmask &= Geodesic.OUT_MASK + result = {'lat1': Math.LatFix(lat1), + 'lon1': lon1 if outmask & Geodesic.LONG_UNROLL else + Math.AngNormalize(lon1), + 'azi1': Math.AngNormalize(azi1), + 'a12': a12} + if outmask & Geodesic.DISTANCE: result['s12'] = s12 + if outmask & Geodesic.LATITUDE: result['lat2'] = lat2 + if outmask & Geodesic.LONGITUDE: result['lon2'] = lon2 + if outmask & Geodesic.AZIMUTH: result['azi2'] = azi2 + if outmask & Geodesic.REDUCEDLENGTH: result['m12'] = m12 + if outmask & Geodesic.GEODESICSCALE: + result['M12'] = M12; result['M21'] = M21 + if outmask & Geodesic.AREA: result['S12'] = S12 + return result
+ +
[docs] def Line(self, lat1, lon1, azi1, + caps = GeodesicCapability.STANDARD | + GeodesicCapability.DISTANCE_IN): + """Return a GeodesicLine object + + :param lat1: latitude of the first point in degrees + :param lon1: longitude of the first point in degrees + :param azi1: azimuth at the first point in degrees + :param caps: the :ref:`capabilities <outmask>` + :return: a :class:`~geographiclib.geodesicline.GeodesicLine` + + This allows points along a geodesic starting at (*lat1*, *lon1*), + with azimuth *azi1* to be found. The default value of *caps* is + STANDARD | DISTANCE_IN, allowing direct geodesic problem to be + solved. + + """ + + from geographiclib.geodesicline import GeodesicLine + return GeodesicLine(self, lat1, lon1, azi1, caps)
+ + def _GenDirectLine(self, lat1, lon1, azi1, arcmode, s12_a12, + caps = GeodesicCapability.STANDARD | + GeodesicCapability.DISTANCE_IN): + """Private: general form of DirectLine""" + from geographiclib.geodesicline import GeodesicLine + # Automatically supply DISTANCE_IN if necessary + if not arcmode: caps |= Geodesic.DISTANCE_IN + line = GeodesicLine(self, lat1, lon1, azi1, caps) + if arcmode: + line.SetArc(s12_a12) + else: + line.SetDistance(s12_a12) + return line + +
[docs] def DirectLine(self, lat1, lon1, azi1, s12, + caps = GeodesicCapability.STANDARD | + GeodesicCapability.DISTANCE_IN): + """Define a GeodesicLine object in terms of the direct geodesic + problem specified in terms of spherical arc length + + :param lat1: latitude of the first point in degrees + :param lon1: longitude of the first point in degrees + :param azi1: azimuth at the first point in degrees + :param s12: the distance from the first point to the second in + meters + :param caps: the :ref:`capabilities <outmask>` + :return: a :class:`~geographiclib.geodesicline.GeodesicLine` + + This function sets point 3 of the GeodesicLine to correspond to + point 2 of the direct geodesic problem. The default value of *caps* + is STANDARD | DISTANCE_IN, allowing direct geodesic problem to be + solved. + + """ + + return self._GenDirectLine(lat1, lon1, azi1, False, s12, caps)
+ +
[docs] def ArcDirectLine(self, lat1, lon1, azi1, a12, + caps = GeodesicCapability.STANDARD | + GeodesicCapability.DISTANCE_IN): + """Define a GeodesicLine object in terms of the direct geodesic + problem specified in terms of spherical arc length + + :param lat1: latitude of the first point in degrees + :param lon1: longitude of the first point in degrees + :param azi1: azimuth at the first point in degrees + :param a12: spherical arc length from the first point to the second + in degrees + :param caps: the :ref:`capabilities <outmask>` + :return: a :class:`~geographiclib.geodesicline.GeodesicLine` + + This function sets point 3 of the GeodesicLine to correspond to + point 2 of the direct geodesic problem. The default value of *caps* + is STANDARD | DISTANCE_IN, allowing direct geodesic problem to be + solved. + + """ + + return self._GenDirectLine(lat1, lon1, azi1, True, a12, caps)
+ +
[docs] def InverseLine(self, lat1, lon1, lat2, lon2, + caps = GeodesicCapability.STANDARD | + GeodesicCapability.DISTANCE_IN): + """Define a GeodesicLine object in terms of the invese geodesic problem + + :param lat1: latitude of the first point in degrees + :param lon1: longitude of the first point in degrees + :param lat2: latitude of the second point in degrees + :param lon2: longitude of the second point in degrees + :param caps: the :ref:`capabilities <outmask>` + :return: a :class:`~geographiclib.geodesicline.GeodesicLine` + + This function sets point 3 of the GeodesicLine to correspond to + point 2 of the inverse geodesic problem. The default value of *caps* + is STANDARD | DISTANCE_IN, allowing direct geodesic problem to be + solved. + + """ + + from geographiclib.geodesicline import GeodesicLine + a12, _, salp1, calp1, _, _, _, _, _, _ = self._GenInverse( + lat1, lon1, lat2, lon2, 0) + azi1 = Math.atan2d(salp1, calp1) + if caps & (Geodesic.OUT_MASK & Geodesic.DISTANCE_IN): + caps |= Geodesic.DISTANCE + line = GeodesicLine(self, lat1, lon1, azi1, caps, salp1, calp1) + line.SetArc(a12) + return line
+ +
[docs] def Polygon(self, polyline = False): + """Return a PolygonArea object + + :param polyline: if True then the object describes a polyline + instead of a polygon + :return: a :class:`~geographiclib.polygonarea.PolygonArea` + + """ + + from geographiclib.polygonarea import PolygonArea + return PolygonArea(self, polyline)
+ + EMPTY = GeodesicCapability.EMPTY + """No capabilities, no output.""" + LATITUDE = GeodesicCapability.LATITUDE + """Calculate latitude *lat2*.""" + LONGITUDE = GeodesicCapability.LONGITUDE + """Calculate longitude *lon2*.""" + AZIMUTH = GeodesicCapability.AZIMUTH + """Calculate azimuths *azi1* and *azi2*.""" + DISTANCE = GeodesicCapability.DISTANCE + """Calculate distance *s12*.""" + STANDARD = GeodesicCapability.STANDARD + """All of the above.""" + DISTANCE_IN = GeodesicCapability.DISTANCE_IN + """Allow distance *s12* to be used as input in the direct geodesic + problem.""" + REDUCEDLENGTH = GeodesicCapability.REDUCEDLENGTH + """Calculate reduced length *m12*.""" + GEODESICSCALE = GeodesicCapability.GEODESICSCALE + """Calculate geodesic scales *M12* and *M21*.""" + AREA = GeodesicCapability.AREA + """Calculate area *S12*.""" + ALL = GeodesicCapability.ALL + """All of the above.""" + LONG_UNROLL = GeodesicCapability.LONG_UNROLL + """Unroll longitudes, rather than reducing them to the range + [-180d,180d]. + + """
+ +Geodesic.WGS84 = Geodesic(Constants.WGS84_a, Constants.WGS84_f) +"""Instantiation for the WGS84 ellipsoid""" +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 00000000..d8a3a9a4 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,114 @@ + + + + + + Overview: module code — Network Wrangler documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/network_wrangler/logger/index.html b/_modules/network_wrangler/logger/index.html new file mode 100644 index 00000000..2a378d01 --- /dev/null +++ b/_modules/network_wrangler/logger/index.html @@ -0,0 +1,169 @@ + + + + + + network_wrangler.logger — Network Wrangler documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for network_wrangler.logger

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Utility functions for logging
+
+import sys, os
+import logging
+from datetime import datetime
+
+__all__ = ["WranglerLogger", "setupLogging"]
+
+WranglerLogger = logging.getLogger("WranglerLogger")
+
+
+
[docs]def setupLogging( + info_log_filename: str = None, + debug_log_filename: str = None, + log_to_console: bool = True +): + """ + Sets up the WranglerLogger w.r.t. the debug file location and if logging to console. + + args: + info_log_filename: the location of the log file that will get created to add the INFO log. + The INFO Log is terse, just gives the bare minimum of details. + debug_log_filename: the location of the log file that will get created to add the DEBUG log. + The DEBUG log is very noisy, for debugging. + log_to_console: if True, logging will go to the console at DEBUG level + """ + # clear handlers if any exist already + WranglerLogger.handlers = [] + + # default is 'debug' so that debug inof won't be ignored is individual handler + WranglerLogger.setLevel(logging.DEBUG) + + FORMAT = logging.Formatter( + "%(asctime)-15s %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S," + ) + + # if info_log_name not provided, default to "network_wrangler.info.log" in current working directory + if not info_log_filename: + info_log_filename = os.path.join( + os.getcwd(), + "network_wrangler_{}.info.log".format(datetime.now().strftime("%Y_%m_%d__%H_%M_%S")), + ) + info_log_handler = logging.StreamHandler(open(info_log_filename, "w")) + info_log_handler.setLevel(logging.INFO) + info_log_handler.setFormatter(FORMAT) + WranglerLogger.addHandler(info_log_handler) + + # create debug file only when debug_log_filename is provided + if debug_log_filename: + debug_log_handler = logging.StreamHandler(open(debug_log_filename, "w")) + debug_log_handler.setLevel(logging.DEBUG) + debug_log_handler.setFormatter(FORMAT) + WranglerLogger.addHandler(debug_log_handler) + + if log_to_console: + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(logging.DEBUG) + console_handler.setFormatter(FORMAT) + WranglerLogger.addHandler(console_handler)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/network_wrangler/projectcard/index.html b/_modules/network_wrangler/projectcard/index.html new file mode 100644 index 00000000..02763965 --- /dev/null +++ b/_modules/network_wrangler/projectcard/index.html @@ -0,0 +1,455 @@ + + + + + + network_wrangler.projectcard — Network Wrangler documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for network_wrangler.projectcard

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+import yaml
+import json
+from typing import Optional, List
+
+from jsonschema import validate
+from jsonschema.exceptions import ValidationError
+from jsonschema.exceptions import SchemaError
+from .logger import WranglerLogger
+
+UNSPECIFIED_PROJECT_NAMES = ["", "TO DO User Define", "USER TO define"]
+
+
+
[docs]class ProjectCard(object): + """ + Representation of a Project Card + + Attributes: + __dict__: Dictionary of project card attributes + valid: Boolean indicating if data conforms to project card data schema + """ + + TRANSIT_CATEGORIES = ["Transit Service Property Change", "Add Transit"] + + # categories that may affect transit, but only as a secondary + # effect of changing roadways + SECONDARY_TRANSIT_CATEGORIES = ["Roadway Deletion", "Parallel Managed Lanes"] + + ROADWAY_CATEGORIES = [ + "Roadway Property Change", + "Roadway Deletion", + "Parallel Managed lanes", + "Add New Roadway", + "Calculated Roadway", + ] + +
[docs] def __init__(self, attribute_dictonary: dict): + """ + Constructor for Project Card object. + + args: + attribute_dictonary: a nested dictionary of project card attributes. + """ + # add these first so they are first on write out + self.project = None + self.tags = "" + self.dependencies = "" + + self.__dict__.update(attribute_dictonary) + self.valid = False
+ + # todo more unstructuring of project card yaml + + def __str__(self): + s = ["{}: {}".format(key, value) for key, value in self.__dict__.items()] + return "\n".join(s) + +
[docs] @staticmethod + def read(card_filename: str, validate: bool = True): + """ + Reads and validates a Project card + + args: + card_filename: The path to the project card file. + validate: Boolean indicating if the project card should be validated. Defaults to True. + + Returns a Project Card object + """ + card_suffix = card_filename.split(".")[-1].lower() + + if card_suffix in ["yaml", "yml"]: + attribute_dictionary = ProjectCard.read_yml(card_filename) + elif card_suffix in ["wrangler", "wr"]: + attribute_dictionary = ProjectCard.read_wrangler_card(card_filename) + else: + msg = "Card should have a suffix of yaml, yml, wrangler, or wr. Found suffix: {}".format( + card_suffix + ) + raise ValueError(msg) + + card = ProjectCard(attribute_dictionary) + + if card.project in UNSPECIFIED_PROJECT_NAMES: + msg = "Card must have valid project name: {}".format(card_filename) + WranglerLogger.error(msg) + raise ValueError(msg) + + card.valid = False + if validate: + card.valid = ProjectCard.validate_project_card_schema(card_filename) + + return card
+ +
[docs] @staticmethod + def read_wrangler_card(w_card_filename: str) -> dict: + """ + Reads wrangler project cards with YAML front matter and then python code. + + Args: + w_card_filename: where the project card is + + Returns: Attribute Dictionary for Project Card + """ + WranglerLogger.debug("Reading Wrangler-Style Project Card") + + with open(w_card_filename, "r") as cardfile: + delim = cardfile.readline() + WranglerLogger.debug("Using delimiter: {}".format(delim)) + _yaml, _pycode = cardfile.read().split(delim) + WranglerLogger.debug("_yaml: {}\n_pycode: {}".format(_yaml, _pycode)) + + attribute_dictionary = yaml.safe_load(_yaml) + attribute_dictionary["file"] = w_card_filename + attribute_dictionary["pycode"] = _pycode.lstrip("\n") + + return attribute_dictionary
+ +
[docs] @staticmethod + def read_yml(card_filename: str) -> dict: + """ + Reads "normal" wrangler project cards defined in YAML. + + Args: + card_filename: file location where the project card is. + + Returns: Attribute Dictionary for Project Card + """ + WranglerLogger.debug("Reading YAML-Style Project Card") + + with open(card_filename, "r") as cardfile: + attribute_dictionary = yaml.safe_load(cardfile) + attribute_dictionary["file"] = card_filename + + return attribute_dictionary
+ +
[docs] def write(self, out_filename: str = None): + """ + Writes project card dictionary to YAML file. + + args: + out_filename: file location to write the project card object as yml. + If not provided, will write to current directory using the project name as the filename. + """ + if not out_filename: + from network_wrangler.utils import make_slug + + out_filename = make_slug(self.project) + ".yml" + + # import collections + # out_dict = collections.OrderedDict() + out_dict = {} + out_dict["project"] = None + out_dict["tags"] = "" + out_dict["dependencies"] = "" + out_dict.update(self.__dict__) + + with open(out_filename, "w") as outfile: + yaml.dump(out_dict, outfile, default_flow_style=False, sort_keys=False) + + WranglerLogger.info("Wrote project card to: {}".format(out_filename))
+ +
[docs] @staticmethod + def validate_project_card_schema( + card_filename: str, + card_schema_filename: str = "project_card.json" + ) -> bool: + """ + Tests project card schema validity by evaluating if it conforms to the schemas + + args: + card_filename: location of project card .yml file + card_schema_filename: location of project card schema to validate against. Defaults to project_card.json. + + returns: boolean + """ + if not os.path.exists(card_schema_filename): + base_path = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "schemas" + ) + card_schema_filename = os.path.join(base_path, card_schema_filename) + + with open(card_schema_filename) as schema_json_file: + schema = json.load(schema_json_file) + + with open(card_filename, "r") as card: + card_json = yaml.safe_load(card) + + try: + validate(card_json, schema) + return True + + except ValidationError as exc: + WranglerLogger.error("Failed Project Card validation: Validation Error") + WranglerLogger.error("Project Card File Loc:{}".format(card_filename)) + WranglerLogger.error("Project Card Schema Loc:{}".format(card_schema_filename)) + WranglerLogger.error(exc.message) + + except SchemaError as exc: + WranglerLogger.error("Failed Project Card schema validation: Schema Error") + WranglerLogger.error("Project Card Schema Loc:{}".format(card_schema_filename)) + WranglerLogger.error(exc.message) + + except yaml.YAMLError as exc: + WranglerLogger.error(exc.message)
+ + + +
[docs] def roadway_attribute_change(self, card: dict): + """ + Probably delete. + Reads a Roadway Attribute Change card. + + args: + card: the project card stored in a dictionary + """ + WranglerLogger.info(card.get("Category"))
+ +
[docs] def new_roadway(self, card: dict): + """ + Probably delete. + Reads a New Roadway card. + + args: + card: the project card stored in a dictionary + """ + WranglerLogger.info(card.get("Category"))
+ +
[docs] def transit_attribute_change(self, card: dict): + """ + Probably delete. + Reads a Transit Service Attribute Change card. + + args: + card: the project card stored in a dictionary + """ + WranglerLogger.info(card.get("Category"))
+ +
[docs] def new_transit_right_of_way(self, card: dict): + """ + Probably delete. + Reads a New Transit Dedicated Right of Way card. + + args: + card: the project card stored in a dictionary + """ + WranglerLogger.info(card.get("Category"))
+ +
[docs] def parallel_managed_lanes(self, card: dict): + """ + Probably delete. + Reads a Parallel Managed lanes card. + + args: + card: the project card stored in a dictionary + """ + WranglerLogger.info(card.get("Category"))
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/network_wrangler/roadwaynetwork/index.html b/_modules/network_wrangler/roadwaynetwork/index.html new file mode 100644 index 00000000..14121e8d --- /dev/null +++ b/_modules/network_wrangler/roadwaynetwork/index.html @@ -0,0 +1,3369 @@ + + + + + + network_wrangler.roadwaynetwork — Network Wrangler documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for network_wrangler.roadwaynetwork

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from __future__ import annotations
+
+import os
+import sys
+import copy
+import numbers
+from random import randint
+from typing import Union
+
+import folium
+import pandas as pd
+import geopandas as gpd
+import json
+import networkx as nx
+import numpy as np
+import osmnx as ox
+
+from geopandas.geodataframe import GeoDataFrame
+
+from pandas.core.frame import DataFrame
+
+from jsonschema import validate
+from jsonschema.exceptions import ValidationError
+from jsonschema.exceptions import SchemaError
+
+from shapely.geometry import Point, LineString
+
+from .logger import WranglerLogger
+from .projectcard import ProjectCard
+from .utils import point_df_to_geojson, link_df_to_json, parse_time_spans
+from .utils import offset_location_reference, haversine_distance, create_unique_shape_id
+from .utils import create_location_reference_from_nodes, create_line_string
+from .utils import update_df
+
+# Coordinate reference system
+CRS = 4326  # AKA EPSG:4326, WGS 1984
+
+# Foreign key variables in data model
+NODE_FOREIGN_KEY = "model_node_id"
+LINK_FOREIGN_KEY = ["A", "B"]
+SHAPE_FOREIGN_KEY = "id"  # formerly UNIQUE_SHAPE_ID
+
+# List of variables that are unique such that they can be directly queried and you should get a single item returned
+UNIQUE_LINK_IDS = ["model_link_id"]
+UNIQUE_NODE_IDS = ["model_node_id"]
+
+# Minimum requirements for selection query
+SELECTION_REQUIRES = ["link"]
+
+# Scalar values added to primary keys for nodes and links for corresponding managed lanes
+# MTC
+MANAGED_LANES_NODE_ID_SCALAR = 4500000
+MANAGED_LANES_LINK_ID_SCALAR = 10000000
+
+# Required attributes to specify for managed lanes
+MANAGED_LANES_REQUIRED_ATTRIBUTES = [
+    "A",
+    "B",
+    "model_link_id",
+    "locationReferences",
+]
+
+# Attributes to copy (keep same) from general purpose lanes to corresponding managed lanes
+KEEP_SAME_ATTRIBUTES_ML_AND_GP = [
+    "distance",
+    "bike_access",
+    "drive_access",
+    "transit_access",
+    "walk_access",
+    "maxspeed",
+    "name",
+    "oneway",
+    "ref",
+    "roadway",
+    "length",
+    "segment_id",
+    "ft",
+    "assignable",
+    'county',
+]
+
+# Mapping of modes to variables in the network
+MODES_TO_NETWORK_LINK_VARIABLES = {
+    "drive": ["drive_access"],
+    "bus": ["bus_only", "drive_access"],
+    "rail": ["rail_only"],
+    "transit": ["bus_only", "rail_only", "drive_access"],
+    "walk": ["walk_access"],
+    "bike": ["bike_access"],
+}
+
+MODES_TO_NETWORK_NODE_VARIABLES = {
+    "drive": ["drive_access"],
+    "rail": ["rail_only", "drive_access"],
+    "bus": ["bus_only", "drive_access"],
+    "transit": ["bus_only", "rail_only", "drive_access"],
+    "walk": ["walk_node"],
+    "bike": ["bike_node"],
+}
+
+# Primary keys used in graph creation
+UNIQUE_LINK_KEY = "model_link_id"
+UNIQUE_NODE_KEY = "model_node_id"
+
+# Shortest Path settings for finding routes
+SEARCH_BREADTH = 5
+MAX_SEARCH_BREADTH = 10
+SP_WEIGHT_FACTOR = 100
+
+
+class NoPathFound(Exception):
+    """Raised when can't find path."""
+
+    pass
+
+
+
[docs]class RoadwayNetwork(object): + """ + Representation of a Roadway Network. + + .. highlight:: python + + Typical usage example: + :: + + net = RoadwayNetwork.read( + link_filename=MY_LINK_FILE, + node_filename=MY_NODE_FILE, + shape_filename=MY_SHAPE_FILE, + shape_foreign_key ='shape_id', + ) + my_selection = { + "link": [{"name": ["I 35E"]}], + "A": {"osm_node_id": "961117623"}, # start searching for segments at A + "B": {"osm_node_id": "2564047368"}, + } + net.select_roadway_features(my_selection) + + my_change = [ + { + 'property': 'lanes', + 'existing': 1, + 'set': 2, + }, + { + 'property': 'drive_access', + 'set': 0, + }, + ] + + my_net.apply_roadway_feature_change( + my_net.select_roadway_features(my_selection), + my_change + ) + + ml_net = net.create_managed_lane_network(in_place=False) + ml_net.is_network_connected(mode="drive")) + _, disconnected_nodes = ml_net.assess_connectivity(mode="walk", ignore_end_nodes=True) + ml_net.write(filename=my_out_prefix, path=my_dir) + + Attributes: + nodes_df (GeoDataFrame): node data + links_df (GeoDataFrame): link data, including start and end + nodes and associated shape + shapes_df (GeoDataFrame): detailed shape data + crs (int): coordinate reference system, ESPG number + node_foreign_key (str): variable linking the node table to the link table + link_foreign_key (list): list of variable linking the link table to the node foreign key + shape_foreign_key (str): variable linking the links table and shape table + unique_link_ids (list): list of variables unique to each link + unique_node_ids (list): list of variables unique to each node + modes_to_network_link_variables (dict): Mapping of modes to link variables in the network + modes_to_network_nodes_variables (dict): Mapping of modes to node variables in the network + managed_lanes_node_id_scalar (int): Scalar values added to primary keys for nodes for + corresponding managed lanes. + managed_lanes_link_id_scalar (int): Scalar values added to primary keys for links for + corresponding managed lanes. + managed_lanes_required_attributes (list): attributes that must be specified in managed + lane projects. + keep_same_attributes_ml_and_gp (list): attributes to copy to managed lanes from parallel + general purpose lanes. + + selections (dict): dictionary storing selections in case they are made repeatedly + """ + +
[docs] def __init__( + self, + nodes: GeoDataFrame, + links: GeoDataFrame, + shapes: GeoDataFrame = None, + node_foreign_key: str = None, + link_foreign_key: str = None, + shape_foreign_key: str = None, + unique_link_key: str = None, + unique_node_key: str = None, + unique_link_ids: list = None, + unique_node_ids: list = None, + crs: int = None, + **kwargs, + ): + """ + Constructor + """ + inputs_valid = [isinstance(x,GeoDataFrame) for x in (nodes, links, shapes)] + if False in inputs_valid: + raise(TypeError("Input nodes ({}), links ({})or shapes ({}) not of required type GeoDataFrame".format(inputs_valid))) + + self.nodes_df = nodes + self.links_df = links + self.shapes_df = shapes + + self.node_foreign_key = NODE_FOREIGN_KEY if node_foreign_key is None else node_foreign_key + self.link_foreign_key = LINK_FOREIGN_KEY if link_foreign_key is None else link_foreign_key + self.shape_foreign_key = SHAPE_FOREIGN_KEY if shape_foreign_key is None else shape_foreign_key + + self.unique_link_key = UNIQUE_LINK_KEY if unique_link_key is None else unique_link_key + self.unique_node_key = UNIQUE_NODE_KEY if unique_node_key is None else unique_node_key + + self.unique_link_ids = UNIQUE_LINK_IDS if unique_link_ids is None else unique_link_ids + self.unique_node_ids = UNIQUE_NODE_IDS if unique_node_ids is None else unique_node_ids + + self.crs = CRS if crs is None else crs + + self.__dict__.update(kwargs) + + # Add non-required fields if they aren't there. + # for field, default_value in RoadwayNetwork.OPTIONAL_FIELDS: + # if field not in self.links_df.columns: + # self.links_df[field] = default_value + if not self.validate_uniqueness(): + raise ValueError("IDs in network not unique") + self.selections = {}
+ +
[docs] @staticmethod + def read( + link_filename: str, + node_filename: str, + shape_filename: str, + fast: bool = True, + crs: int = CRS, + node_foreign_key: str = NODE_FOREIGN_KEY, + link_foreign_key: list = LINK_FOREIGN_KEY, + shape_foreign_key: str = SHAPE_FOREIGN_KEY, + unique_link_key: str = UNIQUE_LINK_KEY, + unique_node_key: str = UNIQUE_NODE_KEY, + unique_link_ids: list = UNIQUE_LINK_IDS, + unique_node_ids: list = UNIQUE_NODE_IDS, + modes_to_network_link_variables: dict = MODES_TO_NETWORK_LINK_VARIABLES, + modes_to_network_nodes_variables: dict = MODES_TO_NETWORK_NODE_VARIABLES, + managed_lanes_link_id_scalar: int = MANAGED_LANES_LINK_ID_SCALAR, + managed_lanes_node_id_scalar: int = MANAGED_LANES_NODE_ID_SCALAR, + managed_lanes_required_attributes: list = MANAGED_LANES_REQUIRED_ATTRIBUTES, + keep_same_attributes_ml_and_gp: list = KEEP_SAME_ATTRIBUTES_ML_AND_GP, + ) -> RoadwayNetwork: + """ + Reads a network from the roadway network standard + Validates that it conforms to the schema + + args: + node_filename: full path to the node file + link_filename: full path to the link file + shape_filename: full path to the shape file + fast: boolean that will skip validation to speed up read time + crs: coordinate reference system, ESPG number + node_foreign_key: variable linking the node table to the link table. + link_foreign_key: + shape_foreign_key: + unique_link_ids: + unique_node_ids: + modes_to_network_link_variables: + modes_to_network_nodes_variables: + managed_lanes_node_id_scalar: + managed_lanes_link_id_scalar: + managed_lanes_required_attributes: + keep_same_attributes_ml_and_gp: + + Returns: a RoadwayNetwork instance + + .. todo:: Turn off fast=True as default + """ + + WranglerLogger.info("Reading RoadwayNetwork") + + nodes_df,links_df,shapes_df = RoadwayNetwork.load_transform_network( + node_filename, + link_filename, + shape_filename, + crs = crs, + node_foreign_key = node_foreign_key, + validate_schema = not fast, + ) + + roadway_network = RoadwayNetwork( + nodes=nodes_df, + links=links_df, + shapes=shapes_df, + crs=crs, + node_foreign_key=node_foreign_key, + link_foreign_key=link_foreign_key, + shape_foreign_key=shape_foreign_key, + unique_link_ids=unique_link_ids, + unique_node_ids=unique_node_ids, + unique_link_key=unique_link_key, + unique_node_key=unique_node_key, + modes_to_network_link_variables=modes_to_network_link_variables, + modes_to_network_nodes_variables=modes_to_network_nodes_variables, + link_filename = link_filename, + node_filename = node_filename, + shape_filename = shape_filename, + ) + + return roadway_network
+ +
[docs] @staticmethod + def load_transform_network( + node_filename: str, + link_filename: str, + shape_filename: str, + crs: int = CRS, + node_foreign_key: str = NODE_FOREIGN_KEY, + validate_schema: bool = True, + **kwargs, + ) -> tuple: + """ + Reads roadway network files from disk and transforms them into GeoDataFrames. + + args: + node_filename: file name for nodes. + link_filename: file name for links. + shape_filename: file name for shapes. + crs: coordinate reference system. Defaults to value in CRS. + node_foreign_key: variable linking the node table to the link table. Defaults + to NODE_FOREIGN_KEY. + validate_schema: boolean indicating if network should be validated to schema. + + returns: tuple of GeodataFrames nodes_df, links_df, shapes_df + """ + WranglerLogger.debug( + "Reading RoadwayNetwork from following files:\n -{}\n -{}\n -{}.".format( + link_filename, node_filename, shape_filename + ) + ) + + if validate_schema: + if not ( + RoadwayNetwork.validate_node_schema(node_filename) + and RoadwayNetwork.validate_link_schema(link_filename) + and RoadwayNetwork.validate_shape_schema(shape_filename) + ): + + raise ValueError("RoadwayNetwork: Data doesn't conform to schema") + + with open(link_filename) as f: + link_json = json.load(f) + + link_properties = pd.DataFrame(link_json) + link_geometries = [ + create_line_string(g["locationReferences"]) for g in link_json + ] + links_df = gpd.GeoDataFrame(link_properties, geometry=link_geometries) + + links_df.crs = crs + # coerce types for booleans which might not have a 1 and are therefore read in as intersection + bool_columns = [ + "rail_only", + "bus_only", + "drive_access", + "bike_access", + "walk_access", + "truck_access", + ] + for bc in list(set(bool_columns) & set(links_df.columns)): + links_df[bc] = links_df[bc].astype(bool) + + shapes_df = gpd.read_file(shape_filename) + shapes_df.dropna(subset=["geometry", "id"], inplace=True) + shapes_df.crs = crs + + # geopandas uses fiona OGR drivers, which doesn't let you have + # a list as a property type. Therefore, must read in node_properties + # separately in a vanilla dataframe and then convert to geopandas + + with open(node_filename) as f: + node_geojson = json.load(f) + + node_properties_df = pd.DataFrame( + [g["properties"] for g in node_geojson["features"]] + ) + + if node_foreign_key not in node_properties_df.columns: + raise ValueError("Specified `node_foreign_key`: {} not found in {}. Available properties: {}".format( + node_foreign_key, + node_filename, + node_properties_df.columns + )) + + node_geometries = [ + Point(g["geometry"]["coordinates"]) for g in node_geojson["features"] + ] + + nodes_df = gpd.GeoDataFrame(node_properties_df, geometry=node_geometries) + + nodes_df.gdf_name = "network_nodes" + + # set a copy of the foreign key to be the index so that the + # variable itself remains queryiable + nodes_df[node_foreign_key + "_idx"] = nodes_df[node_foreign_key] + nodes_df.set_index(node_foreign_key + "_idx", inplace=True) + + nodes_df.crs = crs + nodes_df["X"] = nodes_df["geometry"].apply(lambda g: g.x) + nodes_df["Y"] = nodes_df["geometry"].apply(lambda g: g.y) + + WranglerLogger.info("Read %s links from %s" % (len(links_df), link_filename)) + WranglerLogger.info("Read %s nodes from %s" % (len(nodes_df), node_filename)) + WranglerLogger.info("Read %s shapes from %s" % (len(shapes_df), shape_filename)) + + return nodes_df, links_df, shapes_df
+ + +
[docs] def write(self, path: str = ".", filename: str = None) -> None: + """ + Writes a network in the roadway network standard + + args: + path: the path were the output will be saved + filename: the name prefix of the roadway files that will be generated + """ + + if not os.path.exists(path): + WranglerLogger.debug("\nPath [%s] doesn't exist; creating." % path) + os.mkdir(path) + + if filename: + links_file = os.path.join(path, filename + "_" + "link.json") + nodes_file = os.path.join(path, filename + "_" + "node.geojson") + shapes_file = os.path.join(path, filename + "_" + "shape.geojson") + else: + links_file = os.path.join(path, "link.json") + nodes_file = os.path.join(path, "node.geojson") + shapes_file = os.path.join(path, "shape.geojson") + + link_property_columns = self.links_df.columns.values.tolist() + link_property_columns.remove("geometry") + """ + links_json = link_df_to_json(self.links_df, link_property_columns) + with open(links_file, "w") as f: + json.dump(links_json, f) + """ + links_json = self.links_df[link_property_columns].to_json(orient = "records") + + with open(links_file, 'w') as f: + f.write(links_json) + + # geopandas wont let you write to geojson because + # it uses fiona, which doesn't accept a list as one of the properties + # so need to convert the df to geojson manually first + property_columns = self.nodes_df.columns.values.tolist() + property_columns.remove("geometry") + + nodes_geojson = point_df_to_geojson(self.nodes_df, property_columns) + + with open(nodes_file, "w") as f: + json.dump(nodes_geojson, f) + + self.shapes_df.to_file(shapes_file, driver="GeoJSON")
+ +
[docs] @staticmethod + def roadway_net_to_gdf(roadway_net: RoadwayNetwork) -> gpd.GeoDataFrame: + """ + Turn the roadway network into a GeoDataFrame + args: + roadway_net: the roadway network to export + + returns: shapes dataframe + + .. todo:: Make this much more sophisticated, for example attach link info to shapes + """ + return roadway_net.shapes_df
+ +
[docs] def validate_uniqueness(self) -> bool: + """ + Confirms that the unique identifiers are met. + """ + valid = True + for c in self.unique_link_ids: + if c not in self.links_df.columns: + valid = False + msg = "Network doesn't contain unique link identifier: {}".format(c) + WranglerLogger.error(msg) + if not self.links_df[c].is_unique: + valid = False + msg = "Unique identifier {} is not unique in network links".format(c) + WranglerLogger.error(msg) + for c in self.link_foreign_key: + if c not in self.links_df.columns: + valid = False + msg = "Network doesn't contain link foreign key identifier: {}".format( + c + ) + WranglerLogger.error(msg) + link_foreign_key = self.links_df[self.link_foreign_key].apply( + lambda x: "-".join(x.map(str)), axis=1 + ) + if not link_foreign_key.is_unique: + valid = False + msg = "Foreign key: {} is not unique in network links".format( + self.link_foreign_key + ) + WranglerLogger.error(msg) + for c in self.unique_node_ids: + if c not in self.nodes_df.columns: + valid = False + msg = "Network doesn't contain unique node identifier: {}".format(c) + WranglerLogger.error(msg) + if not self.nodes_df[c].is_unique: + valid = False + msg = "Unique identifier {} is not unique in network nodes".format(c) + WranglerLogger.error(msg) + if self.node_foreign_key not in self.nodes_df.columns: + valid = False + msg = "Network doesn't contain node foreign key identifier: {}".format( + self.node_foreign_key + ) + WranglerLogger.error(msg) + elif not self.nodes_df[self.node_foreign_key].is_unique: + valid = False + msg = "Foreign key: {} is not unique in network nodes".format( + self.node_foreign_key + ) + WranglerLogger.error(msg) + if self.shape_foreign_key not in self.shapes_df.columns: + valid = False + msg = "Network doesn't contain unique shape id: {}".format( + self.shape_foreign_key + ) + WranglerLogger.error(msg) + elif not self.shapes_df[self.shape_foreign_key].is_unique: + valid = False + msg = "Unique key: {} is not unique in network shapes".format( + self.shape_foreign_key + ) + WranglerLogger.error(msg) + return valid
+ +
[docs] @staticmethod + def validate_node_schema( + node_file, schema_location: str = "roadway_network_node.json" + ): + """ + Validate roadway network data node schema and output a boolean + """ + if not os.path.exists(schema_location): + base_path = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "schemas" + ) + schema_location = os.path.join(base_path, schema_location) + + with open(schema_location) as schema_json_file: + schema = json.load(schema_json_file) + + with open(node_file) as node_json_file: + json_data = json.load(node_json_file) + + try: + validate(json_data, schema) + return True + + except ValidationError as exc: + WranglerLogger.error("Failed Node schema validation: Validation Error") + WranglerLogger.error("Node File Loc:{}".format(node_file)) + WranglerLogger.error("Node Schema Loc:{}".format(schema_location)) + WranglerLogger.error(exc.message) + + except SchemaError as exc: + WranglerLogger.error("Invalid Node Schema") + WranglerLogger.error("Node Schema Loc:{}".format(schema_location)) + WranglerLogger.error(json.dumps(exc.message, indent=2)) + + return False
+ + + +
[docs] @staticmethod + def validate_shape_schema( + shape_file, schema_location: str = "roadway_network_shape.json" + ): + """ + Validate roadway network data shape schema and output a boolean + """ + + if not os.path.exists(schema_location): + base_path = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "schemas" + ) + schema_location = os.path.join(base_path, schema_location) + + with open(schema_location) as schema_json_file: + schema = json.load(schema_json_file) + + with open(shape_file) as shape_json_file: + json_data = json.load(shape_json_file) + + try: + validate(json_data, schema) + return True + + except ValidationError as exc: + WranglerLogger.error("Failed Shape schema validation: Validation Error") + WranglerLogger.error("Shape File Loc:{}".format(shape_file)) + WranglerLogger.error("Path:{}".format(exc.path)) + WranglerLogger.error(exc.message) + + except SchemaError as exc: + WranglerLogger.error("Invalid Shape Schema") + WranglerLogger.error("Shape Schema Loc: {}".format(schema_location)) + WranglerLogger.error(json.dumps(exc.message, indent=2)) + + return False
+ +
[docs] def validate_selection( + self, selection: dict, selection_requires: list = SELECTION_REQUIRES + ) -> bool: + """ + Evaluate whetther the selection dictionary contains the + minimum required values. + + Args: + selection: selection dictionary to be evaluated + + Returns: boolean value as to whether the selection dictonary is valid. + """ + if not set(selection_requires).issubset(selection): + err_msg = "Project Card Selection requires: {}".format( + ",".join(selection_requires) + ) + err_msg += ", but selection only contains: {}".format(",".join(selection)) + WranglerLogger.error(err_msg) + raise KeyError(err_msg) + + err = [] + for l in selection["link"]: + for k, v in l.items(): + if k not in self.links_df.columns: + err.append( + "{} specified in link selection but not an attribute in network\n".format( + k + ) + ) + selection_keys = [k for l in selection["link"] for k, v in l.items()] + unique_link_id = bool( + set(self.unique_link_ids).intersection(set(selection_keys)) + ) + + if not unique_link_id: + for k, v in selection["A"].items(): + if k not in self.nodes_df.columns and k != self.node_foreign_key: + err.append( + "{} specified in A node selection but not an attribute in network\n".format( + k + ) + ) + for k, v in selection["B"].items(): + if k not in self.nodes_df.columns and k != self.node_foreign_key: + err.append( + "{} specified in B node selection but not an attribute in network\n".format( + k + ) + ) + if err: + WranglerLogger.error( + "ERROR: Selection variables in project card not found in network" + ) + WranglerLogger.error("\n".join(err)) + WranglerLogger.error( + "--existing node columns:{}".format(" ".join(self.nodes_df.columns)) + ) + WranglerLogger.error( + "--existing link columns:{}".format(" ".join(self.links_df.columns)) + ) + raise ValueError() + else: + return True
+ +
[docs] def orig_dest_nodes_foreign_key( + self, selection: dict, node_foreign_key: str = "" + ) -> tuple: + """ + Returns the foreign key id (whatever is used in the u and v + variables in the links file) for the AB nodes as a tuple. + + Args: + selection : selection dictionary with A and B keys + node_foreign_key: variable name for whatever is used by the u and v variable + in the links_df file. If nothing is specified, assume whatever + default is (usually osm_node_id) + + Returns: tuple of (A_id, B_id) + """ + + if not node_foreign_key: + node_foreign_key = self.node_foreign_key + if len(selection["A"]) > 1: + raise ("Selection A node dictionary should be of length 1") + if len(selection["B"]) > 1: + raise ("Selection B node dictionary should be of length 1") + + A_node_key, A_id = next(iter(selection["A"].items())) + B_node_key, B_id = next(iter(selection["B"].items())) + + if A_node_key != node_foreign_key: + A_id = self.nodes_df[self.nodes_df[A_node_key] == A_id][ + node_foreign_key + ].values[0] + if B_node_key != node_foreign_key: + B_id = self.nodes_df[self.nodes_df[B_node_key] == B_id][ + node_foreign_key + ].values[0] + + return (A_id, B_id)
+ +
[docs] @staticmethod + def get_managed_lane_node_ids( + nodes_list: list, scalar: int = MANAGED_LANES_NODE_ID_SCALAR + ): + """ + Transform a list of node IDS by a scalar. + ..todo #237 what if node ids are not a number? + + Args: + nodes_list: list of integers + scalar: value to add to node IDs + + Returns: list of integers + """ + return [x + scalar for x in nodes_list]
+ +
[docs] @staticmethod + def ox_graph( + nodes_df: GeoDataFrame, + links_df: GeoDataFrame, + node_foreign_key: str = NODE_FOREIGN_KEY, + link_foreign_key: list = LINK_FOREIGN_KEY, + unique_link_key: str = UNIQUE_LINK_KEY, + ): + """ + create an osmnx-flavored network graph + + osmnx doesn't like values that are arrays, so remove the variables + that have arrays. osmnx also requires that certain variables + be filled in, so do that too. + + Args: + nodes_df : GeoDataFrame of nodes + link_df : GeoDataFrame of links + node_foreign_key: field referenced in `link_foreign_key` + link_foreign_key: list of attributes that define the link start and end nodes to the node foreign key + unique_link_key: primary key for links + + Returns: a networkx multidigraph + """ + WranglerLogger.debug("starting ox_graph()") + + if "inboundReferenceIds" in nodes_df.columns: + graph_nodes = nodes_df.copy().drop( + ["inboundReferenceIds", "outboundReferenceIds"], axis=1 + ) + else: + graph_nodes = nodes_df.copy() + + graph_nodes.gdf_name = "network_nodes" + WranglerLogger.debug("GRAPH NODES: {}".format(graph_nodes.columns)) + graph_nodes["id"] = graph_nodes[node_foreign_key] + + graph_nodes["x"] = graph_nodes["X"] + graph_nodes["y"] = graph_nodes["Y"] + + if "osm_link_id" in links_df.columns: + graph_links = links_df.copy().drop( + ["osm_link_id", "locationReferences"], axis=1 + ) + else: + graph_links = links_df.copy().drop( + ["locationReferences"], axis=1 + ) + + # have to change this over into u,v b/c this is what osm-nx is expecting + graph_links["u"] = graph_links[link_foreign_key[0]] + graph_links["v"] = graph_links[link_foreign_key[1]] + graph_links["id"] = graph_links[unique_link_key] + graph_links["key"] = graph_links[unique_link_key] + + WranglerLogger.debug("starting ox.gdfs_to_graph()") + try: + if (int(ox.__version__.split('.')[0]) >= 1): + # index required in newer osmnx + graph_links = graph_links.set_index(["u", "v", "key"]) + G = ox.graph_from_gdfs(graph_nodes, graph_links) + except AttributeError: + WranglerLogger.debug( + "Please upgrade your OSMNX package. For now, using depricated osmnx.gdfs_to_graph because osmnx.graph_from_gdfs not found" + ) + G = ox.gdfs_to_graph(graph_nodes, graph_links) + + WranglerLogger.debug("finished ox.gdfs_to_graph()") + return G
+ + + +
[docs] def build_selection_key(self, selection_dict: dict) -> tuple: + """ + Selections are stored by a key combining the query and the A and B ids. + This method combines the two for you based on the selection dictionary. + + Args: + selection_dictonary: Selection Dictionary + + Returns: Tuple serving as the selection key. + + """ + sel_query = ProjectCard.build_link_selection_query( + selection=selection_dict, + unique_link_ids=self.unique_link_ids, + ) + + if self.selection_has_unique_link_id(selection_dict): + return sel_query + + A_id, B_id = self.orig_dest_nodes_foreign_key(selection_dict) + return (sel_query, A_id, B_id)
+ + @staticmethod + def _get_fk_nodes( + _links: gpd.GeoDataFrame, link_foreign_key: list = LINK_FOREIGN_KEY + ): + """Find the nodes for the candidate links. + """ + _n = list(set([i for fk in link_foreign_key for i in list(_links[fk])])) + # WranglerLogger.debug("Node foreign key list: {}".format(_n)) + return _n + +
[docs] def shortest_path( + self, + graph_links_df: gpd.GeoDataFrame, + O_id, + D_id, + nodes_df: gpd.GeoDataFrame = None, + weight_column: str = "i", + weight_factor: float = SP_WEIGHT_FACTOR, + ) -> tuple: + """ + + Args: + graph_links_df: + O_id: foreign key for start node + D_id: foreign key for end node + nodes_df: optional nodes df, otherwise will use network instance + weight_column: column to use as a weight, defaults to "i" + weight_factor: any additional weighting to multiply the weight column by, defaults to SP_WEIGHT_FACTOR + + Returns: tuple with length of four + - Boolean if shortest path found + - nx Directed graph of graph links + - route of shortest path nodes as List + - links in shortest path selected from links_df + """ + WranglerLogger.debug( + "Calculating shortest path from {} to {} using {} as weight with a factor of {}".format( + O_id, D_id, weight_column, weight_factor + ) + ) + + # Prep Graph Links + if weight_column not in graph_links_df.columns: + WranglerLogger.warning( + "{} not in graph_links_df so adding and initializing to 1.".format( + weight_column + ) + ) + graph_links_df[weight_column] = 1 + + graph_links_df.loc[:, "weight"] = 1 + ( + graph_links_df[weight_column] * weight_factor + ) + + # Select Graph Nodes + node_list_foreign_keys = RoadwayNetwork._get_fk_nodes( + graph_links_df, link_foreign_key=self.link_foreign_key + ) + + if O_id not in node_list_foreign_keys: + msg = "O_id: {} not in Graph for finding shortest Path".format(O_id) + WranglerLogger.error(msg) + raise ValueError(msg) + if D_id not in node_list_foreign_keys: + msg = "D_id: {} not in Graph for finding shortest Path".format(D_id) + WranglerLogger.error(msg) + raise ValueError(msg) + + if not nodes_df: + nodes_df = self.nodes_df + graph_nodes_df = nodes_df.loc[node_list_foreign_keys] + + # Create Graph + WranglerLogger.debug("Creating network graph") + G = RoadwayNetwork.ox_graph( + graph_nodes_df, + graph_links_df, + node_foreign_key=self.node_foreign_key, + link_foreign_key=self.link_foreign_key, + unique_link_key=self.unique_link_key, + ) + + try: + sp_route = nx.shortest_path(G, O_id, D_id, weight="weight") + WranglerLogger.debug("Shortest path successfully routed") + except nx.NetworkXNoPath: + WranglerLogger.debug("No SP from {} to {} Found.".format(O_id, D_id)) + return False, G, graph_links_df, None, None + + sp_links = graph_links_df[ + graph_links_df["A"].isin(sp_route) & graph_links_df["B"].isin(sp_route) + ] + + return True, G, graph_links_df, sp_route, sp_links
+ + + +
[docs] def select_roadway_features( + self, + selection: dict, + search_mode="drive", + force_search=False, + sp_weight_factor = None, + ) -> GeoDataFrame: + """ + Selects roadway features that satisfy selection criteria + + Example usage: + net.select_roadway_features( + selection = [ { + # a match condition for the from node using osm, + # shared streets, or model node number + 'from': {'osm_model_link_id': '1234'}, + # a match for the to-node.. + 'to': {'shstid': '4321'}, + # a regex or match for facility condition + # could be # of lanes, facility type, etc. + 'facility': {'name':'Main St'}, + }, ... ]) + + Args: + selection : dictionary with keys for: + A - from node + B - to node + link - which includes at least a variable for `name` + search_mode: mode which you are searching for; defaults to "drive" + force_search: boolean directing method to perform search even if one + with same selection dict is stored from a previous search. + sp_weight_factor: multiple used to discourage shortest paths which + meander from original search returned from name or ref query. + If not set here, will default to value of sp_weight_factor in + RoadwayNetwork instance. If not set there, will defaul to SP_WEIGHT_FACTOR. + + Returns: a list of link IDs in selection + """ + WranglerLogger.debug("validating selection") + self.validate_selection(selection) + + if not sp_weight_factor: + sp_weight_factor = self.__dict__.get("sp_weight_factor") + if not sp_weight_factor: + sp_weight_factor = SP_WEIGHT_FACTOR + + # create a unique key for the selection so that we can cache it + sel_key = self.build_selection_key(selection) + WranglerLogger.debug("Selection Key: {}".format(sel_key)) + + # if this selection has been queried before, just return the + # previously selected links + if sel_key in self.selections and not force_search: + if self.selections[sel_key]["selection_found"]: + return self.selections[sel_key]["selected_links"].index.tolist() + else: + msg = "Selection previously queried but no selection found" + WranglerLogger.error(msg) + raise Exception(msg) + self.selections[sel_key] = {} + self.selections[sel_key]["selection_found"] = False + + unique_link_identifer_in_selection = self.selection_has_unique_link_id(selection) + + if not unique_link_identifer_in_selection: + A_id, B_id = self.orig_dest_nodes_foreign_key(selection) + # identify candidate links which match the initial query + # assign them as iteration = 0 + # subsequent iterations that didn't match the query will be + # assigned a heigher weight in the shortest path + WranglerLogger.debug("Building selection query") + # build a selection query based on the selection dictionary + + sel_query = ProjectCard.build_link_selection_query( + selection=selection, + unique_link_ids=self.unique_link_ids, + mode=self.modes_to_network_link_variables[search_mode], + ) + WranglerLogger.debug("Selecting features:\n{}".format(sel_query)) + + self.selections[sel_key]["candidate_links"] = self.links_df.query( + sel_query, engine="python" + ) + WranglerLogger.debug("Completed query") + candidate_links = self.selections[sel_key][ + "candidate_links" + ] # b/c too long to keep that way + + candidate_links["i"] = 0 + + if len(candidate_links.index) == 0 and unique_link_identifer_in_selection: + msg = "No links found based on unique link identifiers.\nSelection Failed." + WranglerLogger.error(msg) + raise Exception(msg) + + if len(candidate_links.index) == 0: + WranglerLogger.debug( + "No candidate links in initial search.\nRetrying query using 'ref' instead of 'name'" + ) + # if the query doesn't come back with something from 'name' + # try it again with 'ref' instead + selection_has_name_key = any("name" in d for d in selection["link"]) + + if not selection_has_name_key: + msg = "Not able to complete search using 'ref' instead of 'name' because 'name' not in search." + WranglerLogger.error(msg) + raise Exception(msg) + + if not "ref" in self.links_df.columns: + msg = "Not able to complete search using 'ref' because 'ref' not in network." + WranglerLogger.error(msg) + raise Exception(msg) + + WranglerLogger.debug("Trying selection query replacing 'name' with 'ref'") + sel_query = sel_query.replace("name", "ref") + + self.selections[sel_key]["candidate_links"] = self.links_df.query( + sel_query, engine="python" + ) + candidate_links = self.selections[sel_key]["candidate_links"] + + candidate_links["i"] = 0 + + if len(candidate_links.index) == 0: + msg = "No candidate links in search using either 'name' or 'ref' in query.\nSelection Failed." + WranglerLogger.error(msg) + raise Exception(msg) + + if unique_link_identifer_in_selection: + # unique identifier exists and no need to go through big search + self.selections[sel_key]["selected_links"] = self.selections[sel_key][ + "candidate_links" + ] + self.selections[sel_key]["selection_found"] = True + + return self.selections[sel_key]["selected_links"].index.tolist() + + else: + WranglerLogger.debug("Not a unique ID selection, conduct search.") + ( + self.selections[sel_key]["graph"], + self.selections[sel_key]["candidate_links"], + self.selections[sel_key]["route"], + self.selections[sel_key]["links"], + ) = self.path_search( + self.selections[sel_key]["candidate_links"], + A_id, + B_id, + weight_factor=sp_weight_factor, + ) + + if len(selection["link"]) == 1: + self.selections[sel_key]["selected_links"] = self.selections[sel_key][ + "links" + ] + + # Conduct a "selection on the selection" if have additional requirements to satisfy + else: + resel_query = ProjectCard.build_link_selection_query( + selection=selection, + unique_link_ids=self.unique_link_ids, + mode=self.modes_to_network_link_variables[search_mode], + ignore=["name"], + ) + WranglerLogger.debug("Reselecting features:\n{}".format(resel_query)) + self.selections[sel_key]["selected_links"] = self.selections[sel_key][ + "links" + ].query(resel_query, engine="python") + + self.selections[sel_key]["selection_found"] = True + return self.selections[sel_key]["selected_links"].index.tolist()
+ +
[docs] def validate_properties( + self, + properties: dict, + ignore_existing: bool = False, + require_existing_for_change: bool = False, + ) -> bool: + """ + If there are change or existing commands, make sure that that + property exists in the network. + + Args: + properties : properties dictionary to be evaluated + ignore_existing: If True, will only warn about properties + that specify an "existing" value. If False, will fail. + require_existing_for_change: If True, will fail if there isn't + a specified value in theproject card for existing when a + change is specified. + + Returns: boolean value as to whether the properties dictonary is valid. + """ + + validation_error_message = [] + + for p in properties: + if p["property"] not in self.links_df.columns: + if p.get("change"): + validation_error_message.append( + '"Change" is specified for attribute {}, but doesn\'t exist in base network\n'.format( + p["property"] + ) + ) + + if p.get("existing") and not ignore_existing: + validation_error_message.append( + '"Existing" is specified for attribute {}, but doesn\'t exist in base network\n'.format( + p["property"] + ) + ) + elif p.get("existing"): + WranglerLogger.warning( + '"Existing" is specified for attribute {}, but doesn\'t exist in base network\n'.format( + p["property"] + ) + ) + + if p.get("change") and not p.get("existing"): + if require_existing_for_change: + validation_error_message.append( + '"Change" is specified for attribute {}, but there isn\'t a value for existing.\nTo proceed, run with the setting require_existing_for_change=False'.format( + p["property"] + ) + ) + else: + WranglerLogger.warning( + '"Change" is specified for attribute {}, but there isn\'t a value for existing.\n'.format( + p["property"] + ) + ) + + if validation_error_message: + WranglerLogger.error(" ".join(validation_error_message)) + raise ValueError()
+ +
[docs] def apply(self, project_card_dictionary: dict): + """ + Wrapper method to apply a project to a roadway network. + + Args: + project_card_dictionary: dict + a dictionary of the project card object + + """ + + WranglerLogger.info( + "Applying Project to Roadway Network: {}".format( + project_card_dictionary["project"] + ) + ) + + def _apply_individual_change(project_dictionary: dict): + + if project_dictionary["category"].lower() == "roadway property change": + self.apply_roadway_feature_change( + self.select_roadway_features(project_dictionary["facility"]), + project_dictionary["properties"], + ) + elif project_dictionary["category"].lower() == "parallel managed lanes": + self.apply_managed_lane_feature_change( + self.select_roadway_features(project_dictionary["facility"]), + project_dictionary["properties"], + ) + elif project_dictionary["category"].lower() == "add new roadway": + self.add_new_roadway_feature_change( + project_dictionary.get("links"), project_dictionary.get("nodes") + ) + elif project_dictionary["category"].lower() == "roadway deletion": + self.delete_roadway_feature_change( + project_dictionary.get("links"), project_dictionary.get("nodes") + ) + elif project_dictionary["category"].lower() == "calculated roadway": + self.apply_python_calculation(project_dictionary["pycode"]) + else: + raise (BaseException) + + if project_card_dictionary.get("changes"): + for project_dictionary in project_card_dictionary["changes"]: + _apply_individual_change(project_dictionary) + else: + _apply_individual_change(project_card_dictionary)
+ +
[docs] def apply_python_calculation( + self, pycode: str, in_place: bool = True + ) -> Union(None, RoadwayNetwork): + """ + Changes roadway network object by executing pycode. + + Args: + pycode: python code which changes values in the roadway network object + in_place: update self or return a new roadway network object + """ + exec(pycode)
+ +
[docs] def apply_roadway_feature_change( + self, link_idx: list, properties: dict, in_place: bool = True + ) -> Union(None, RoadwayNetwork): + """ + Changes the roadway attributes for the selected features based on the + project card information passed + + Args: + link_idx : list + lndices of all links to apply change to + properties : list of dictionarys + roadway properties to change + in_place: boolean + update self or return a new roadway network object + """ + + # check if there are change or existing commands that that property + # exists in the network + # if there is a set command, add that property to network + self.validate_properties(properties) + + for i, p in enumerate(properties): + attribute = p["property"] + + # if project card specifies an existing value in the network + # check and see if the existing value in the network matches + if p.get("existing"): + network_values = self.links_df.loc[link_idx, attribute].tolist() + if not set(network_values).issubset([p.get("existing")]): + WranglerLogger.warning( + "Existing value defined for {} in project card does " + "not match the value in the roadway network for the " + "selected links".format(attribute) + ) + + if in_place: + if "set" in p.keys(): + self.links_df.loc[link_idx, attribute] = p["set"] + else: + self.links_df.loc[link_idx, attribute] = ( + self.links_df.loc[link_idx, attribute] + p["change"] + ) + else: + if i == 0: + updated_network = copy.deepcopy(self) + + if "set" in p.keys(): + updated_network.links_df.loc[link_idx, attribute] = p["set"] + else: + updated_network.links_df.loc[link_idx, attribute] = ( + updated_network.links_df.loc[link_idx, attribute] + p["change"] + ) + + if i == len(properties) - 1: + return updated_network
+ +
[docs] def apply_managed_lane_feature_change( + self, link_idx: list, properties: dict, in_place: bool = True + ) -> Union(None, RoadwayNetwork): + """ + Apply the managed lane feature changes to the roadway network + + Args: + link_idx : list of lndices of all links to apply change to + properties : list of dictionarys roadway properties to change + in_place: boolean to indicate whether to update self or return + a new roadway network object + + .. todo:: decide on connectors info when they are more specific in project card + """ + + # add ML flag + if "managed" in self.links_df.columns: + self.links_df.loc[link_idx, "managed"] = 1 + else: + self.links_df["managed"] = 0 + self.links_df.loc[link_idx, "managed"] = 1 + + p = 1 + + for p in properties: + attribute = p["property"] + attr_value = "" + + for idx in link_idx: + if "group" in p.keys(): + attr_value = {} + + if "set" in p.keys(): + attr_value["default"] = p["set"] + elif "change" in p.keys(): + attr_value["default"] = ( + self.links_df.at[idx, attribute] + p["change"] + ) + + attr_value["timeofday"] = [] + + for g in p["group"]: + category = g["category"] + for tod in g["timeofday"]: + if "set" in tod.keys(): + attr_value["timeofday"].append( + { + "category": category, + "time": parse_time_spans(tod["time"]), + "value": tod["set"], + } + ) + elif "change" in tod.keys(): + attr_value["timeofday"].append( + { + "category": category, + "time": parse_time_spans(tod["time"]), + "value": self.links_df.at[idx, attribute] + + tod["change"], + } + ) + + elif "timeofday" in p.keys(): + attr_value = {} + + if "set" in p.keys(): + attr_value["default"] = p["set"] + elif "change" in p.keys(): + attr_value["default"] = ( + self.links_df.at[idx, attribute] + p["change"] + ) + + attr_value["timeofday"] = [] + + for tod in p["timeofday"]: + if "set" in tod.keys(): + attr_value["timeofday"].append( + { + "time": parse_time_spans(tod["time"]), + "value": tod["set"], + } + ) + elif "change" in tod.keys(): + attr_value["timeofday"].append( + { + "time": parse_time_spans(tod["time"]), + "value": self.links_df.at[idx, attribute] + + tod["change"], + } + ) + elif "set" in p.keys(): + attr_value = p["set"] + + elif "change" in p.keys(): + attr_value = self.links_df.at[idx, attribute] + p["change"] + + if in_place: + if attribute in self.links_df.columns and not isinstance( + attr_value, numbers.Number + ): + # if the attribute already exists + # and the attr value we are trying to set is not numeric + # then change the attribute type to object + self.links_df[attribute] = self.links_df[attribute].astype( + object + ) + + if attribute not in self.links_df.columns: + # if it is a new attribute then initialize with NaN values + self.links_df[attribute] = "NaN" + + self.links_df.at[idx, attribute] = attr_value + + else: + if i == 1: + updated_network = copy.deepcopy(self) + + if attribute in self.links_df.columns and not isinstance( + attr_value, numbers.Number + ): + # if the attribute already exists + # and the attr value we are trying to set is not numeric + # then change the attribute type to object + updated_network.links_df[attribute] = updated_network.links_df[ + attribute + ].astype(object) + + if attribute not in updated_network.links_df.columns: + # if it is a new attribute then initialize with NaN values + updated_network.links_df[attribute] = "NaN" + + updated_network.links_df.at[idx, attribute] = attr_value + + if p == len(properties): + return updated_network + else: + p = p + 1
+ +
[docs] def add_new_roadway_feature_change(self, links: dict, nodes: dict) -> None: + """ + add the new roadway features defined in the project card. + new shapes are also added for the new roadway links. + + args: + links : list of dictionaries + nodes : list of dictionaries + + returns: None + + .. todo:: validate links and nodes dictionary + """ + + def _add_dict_to_df(df, new_dict): + df_column_names = df.columns + new_row_to_add = {} + + # add the fields from project card that are in the network + for property in df_column_names: + if property in new_dict.keys(): + if df[property].dtype == np.float64: + value = pd.to_numeric(new_dict[property], downcast="float") + elif (df[property].dtype == np.int64) | (df[property].dtype == np.int32): + value = pd.to_numeric(new_dict[property], downcast="integer") + else: + value = str(new_dict[property]) + else: + value = "" + + new_row_to_add[property] = value + + # add the fields from project card that are NOT in the network + for key, value in new_dict.items(): + if key not in df_column_names: + new_row_to_add[key] = new_dict[key] + + out_df = df.append(new_row_to_add, ignore_index=True) + return out_df + + if nodes is not None: + for node in nodes: + if node.get(self.node_foreign_key) is None: + msg = "New link to add doesn't contain link foreign key identifier: {}".format( + self.node_foreign_key + ) + WranglerLogger.error(msg) + raise ValueError(msg) + + node_query = ( + self.unique_node_key + " == " + str(node[self.node_foreign_key]) + ) + if not self.nodes_df.query(node_query, engine="python").empty: + msg = "Node with id = {} already exist in the network".format( + node[self.node_foreign_key] + ) + WranglerLogger.error(msg) + raise ValueError(msg) + + for node in nodes: + node["new_node"] = 1 + self.nodes_df = _add_dict_to_df(self.nodes_df, node) + + # add geometry for new nodes + self.nodes_df["geometry"] = self.nodes_df.apply( + lambda x: Point(x["X"], x["Y"]) + if x["new_node"] == 1 + else x["geometry"], + axis=1, + ) + + self.nodes_df.drop(["new_node"], axis=1, inplace=True) + + if links is not None: + for link in links: + for key in self.link_foreign_key: + if link.get(key) is None: + msg = "New link to add doesn't contain link foreign key identifier: {}".format( + key + ) + WranglerLogger.error(msg) + raise ValueError(msg) + + ab_query = "A == " + str(link["A"]) + " and B == " + str(link["B"]) + + if not self.links_df.query(ab_query, engine="python").empty: + msg = "Link with A = {} and B = {} already exist in the network".format( + link["A"], link["B"] + ) + WranglerLogger.error(msg) + raise ValueError(msg) + + if self.nodes_df[ + self.nodes_df[self.unique_node_key] == link["A"] + ].empty: + msg = "New link to add has A node = {} but the node does not exist in the network".format( + link["A"] + ) + WranglerLogger.error(msg) + raise ValueError(msg) + + if self.nodes_df[ + self.nodes_df[self.unique_node_key] == link["B"] + ].empty: + msg = "New link to add has B node = {} but the node does not exist in the network".format( + link["B"] + ) + WranglerLogger.error(msg) + raise ValueError(msg) + + for link in links: + link["new_link"] = 1 + self.links_df = _add_dict_to_df(self.links_df, link) + + # add location reference and geometry for new links + self.links_df["locationReferences"] = self.links_df.apply( + lambda x: create_location_reference_from_nodes( + self.nodes_df[ + self.nodes_df[self.node_foreign_key] == x["A"] + ].squeeze(), + self.nodes_df[ + self.nodes_df[self.node_foreign_key] == x["B"] + ].squeeze(), + ) + if x["new_link"] == 1 + else x["locationReferences"], + axis=1, + ) + self.links_df["geometry"] = self.links_df.apply( + lambda x: create_line_string(x["locationReferences"]) + if x["new_link"] == 1 + else x["geometry"], + axis=1, + ) + + self.links_df[self.shape_foreign_key] = self.links_df.apply( + lambda x: create_unique_shape_id(x["geometry"]) + if x["new_link"] == 1 + else x[self.shape_foreign_key], + axis=1, + ) + + # add new shapes + added_links = self.links_df[self.links_df["new_link"] == 1] + + added_shapes_df = pd.DataFrame({"geometry": added_links["geometry"]}) + added_shapes_df[self.shape_foreign_key] = added_shapes_df["geometry"].apply( + lambda x: create_unique_shape_id(x) + ) + self.shapes_df = self.shapes_df.append(added_shapes_df) + + self.links_df.drop(["new_link"], axis=1, inplace=True)
+ +
[docs] def delete_roadway_feature_change( + self, links: dict, nodes: dict, ignore_missing=True + ) -> None: + """ + delete the roadway features defined in the project card. + valid links and nodes defined in the project gets deleted + and shapes corresponding to the deleted links are also deleted. + + Args: + links : dict + list of dictionaries + nodes : dict + list of dictionaries + ignore_missing: bool + If True, will only warn about links/nodes that are missing from + network but specified to "delete" in project card + If False, will fail. + """ + + missing_error_message = [] + + if links is not None: + shapes_to_delete = [] + for key, val in links.items(): + missing_links = [v for v in val if v not in self.links_df[key].tolist()] + if missing_links: + message = "Links attribute {} with values as {} does not exist in the network\n".format( + key, missing_links + ) + if ignore_missing: + WranglerLogger.warning(message) + else: + missing_error_message.append(message) + + deleted_links = self.links_df[self.links_df[key].isin(val)] + shapes_to_delete.extend(deleted_links[self.shape_foreign_key].tolist()) + + self.links_df.drop( + self.links_df.index[self.links_df[key].isin(val)], inplace=True + ) + + self.shapes_df.drop( + self.shapes_df.index[ + self.shapes_df[self.shape_foreign_key].isin(shapes_to_delete) + ], + inplace=True, + ) + + if nodes is not None: + for key, val in nodes.items(): + missing_nodes = [v for v in val if v not in self.nodes_df[key].tolist()] + if missing_nodes: + message = "Nodes attribute {} with values as {} does not exist in the network\n".format( + key, missing_links + ) + if ignore_missing: + WranglerLogger.warning(message) + else: + missing_error_message.append(message) + + self.nodes_df = self.nodes_df[~self.nodes_df[key].isin(val)] + + if missing_error_message: + WranglerLogger.error(" ".join(missing_error_message)) + raise ValueError()
+ +
[docs] def get_property_by_time_period_and_group( + self, prop, time_period=None, category=None, default_return=None + ): + """ + Return a series for the properties with a specific group or time period. + + args + ------ + prop: str + the variable that you want from network + time_period: list(str) + the time period that you are querying for + i.e. ['16:00', '19:00'] + category: str or list(str)(Optional) + the group category + i.e. "sov" + + or + + list of group categories in order of search, i.e. + ["hov3","hov2"] + default_return: what to return if variable or time period not found. Default is None. + + returns + -------- + pandas series + """ + + if prop not in list(self.links_df.columns): + WranglerLogger.warning("Property {} not in links to split, returning as default value: {}".format(prop, default_value)) + return pd.Series([default_return]*len(self.link_df)) + + def _get_property( + v, + time_spans=None, + category=None, + return_partial_match: bool = False, + partial_match_minutes: int = 60, + ): + """ + + .. todo:: return the time period with the largest overlap + + """ + + if category and not time_spans: + WranglerLogger.error( + "\nShouldn't have a category group without time spans" + ) + raise ValueError("Shouldn't have a category group without time spans") + + # simple case + if type(v) in (int, float, str): + return v + + if not category: + category = ["default"] + elif isinstance(category, str): + category = [category] + search_cats = [c.lower() for c in category] + + # if no time or group specified, but it is a complex link situation + if not time_spans: + if "default" in v.keys(): + return v["default"] + else: + WranglerLogger.debug("variable: ".format(v)) + msg = "Variable {} is more complex in network than query".format(v) + WranglerLogger.error(msg) + raise ValueError(msg) + + if v.get("timeofday"): + categories = [] + for tg in v["timeofday"]: + if ((time_spans[0] >= tg["time"][0]) and ( + time_spans[1] <= tg["time"][1]) and ( + time_spans[0] <= time_spans[1]) + ): + if tg.get("category"): + categories += tg["category"] + for c in search_cats: + print("CAT:", c, tg["category"]) + if c in tg["category"]: + # print("Var:", v) + # print( + # "RETURNING:", time_spans, category, tg["value"] + # ) + return tg["value"] + else: + # print("Var:", v) + # print("RETURNING:", time_spans, category, tg["value"]) + return tg["value"] + + if ((time_spans[0] >= tg["time"][0]) and ( + time_spans[1] <= tg["time"][1]) and ( + time_spans[0] > time_spans[1]) and ( + tg["time"][0] > tg["time"][1]) + ): + if tg.get("category"): + categories += tg["category"] + for c in search_cats: + print("CAT:", c, tg["category"]) + if c in tg["category"]: + # print("Var:", v) + # print( + # "RETURNING:", time_spans, category, tg["value"] + # ) + return tg["value"] + else: + # print("Var:", v) + # print("RETURNING:", time_spans, category, tg["value"]) + return tg["value"] + + # if there isn't a fully matched time period, see if there is an overlapping one + # right now just return the first overlapping ones + # TODO return the time period with the largest overlap + + if ( + (time_spans[0] >= tg["time"][0]) + and (time_spans[0] <= tg["time"][1]) + ) or ( + (time_spans[1] >= tg["time"][0]) + and (time_spans[1] <= tg["time"][1]) + ): + overlap_minutes = max( + 0, + min(tg["time"][1], time_spans[1]) + - max(time_spans[0], tg["time"][0]), + ) + # print("OLM",overlap_minutes) + if not return_partial_match and overlap_minutes > 0: + WranglerLogger.debug( + "Couldn't find time period consistent with {}, but found a partial match: {}. Consider allowing partial matches using 'return_partial_match' keyword or updating query.".format( + time_spans, tg["time"] + ) + ) + elif ( + overlap_minutes < partial_match_minutes + and overlap_minutes > 0 + ): + WranglerLogger.debug( + "Time period: {} overlapped less than the minimum number of minutes ({}<{}) to be considered a match with time period in network: {}.".format( + time_spans, + overlap_minutes, + partial_match_minutes, + tg["time"], + ) + ) + elif overlap_minutes > 0: + WranglerLogger.debug( + "Returning a partial time period match. Time period: {} overlapped the minimum number of minutes ({}>={}) to be considered a match with time period in network: {}.".format( + time_spans, + overlap_minutes, + partial_match_minutes, + tg["time"], + ) + ) + if tg.get("category"): + categories += tg["category"] + for c in search_cats: + print("CAT:", c, tg["category"]) + if c in tg["category"]: + # print("Var:", v) + # print( + # "RETURNING:", + # time_spans, + # category, + # tg["value"], + # ) + return tg["value"] + else: + # print("Var:", v) + # print("RETURNING:", time_spans, category, tg["value"]) + return tg["value"] + + """ + WranglerLogger.debug( + "\nCouldn't find time period for {}, returning default".format( + str(time_spans) + ) + ) + """ + if "default" in v.keys(): + # print("Var:", v) + # print("RETURNING:", time_spans, v["default"]) + return v["default"] + else: + # print("Var:", v) + WranglerLogger.error( + "\nCan't find default; must specify a category in {}".format( + str(categories) + ) + ) + raise ValueError( + "Can't find default, must specify a category in: {}".format( + str(categories) + ) + ) + + time_spans = parse_time_spans(time_period) + + return self.links_df[prop].apply( + _get_property, time_spans=time_spans, category=category + )
+ +
[docs] def update_distance( + self, + links_df: GeoDataFrame = None, + use_shapes: bool = False, + units: str = "miles", + network_variable: str = "distance", + overwrite: bool = True, + inplace = True + ): + """ + Calculate link distance in specified units to network variable using either straight line + distance or (if specified) shape distance if available. + + Args: + links_df: Links GeoDataFrame. Useful if want to update a portion of network links + (i.e. only centroid connectors). If not provided, will use entire self.links_df. + use_shapes: if True, will add length information from self.shapes_df rather than crow-fly. + If no corresponding shape found in self.shapes_df, will default to crow-fly. + units: units to use. Defaults to the standard unit of miles. Available units: "meters", "miles". + network_variable: variable to store link distance in. Defaults to "distance". + overwrite: Defaults to True and will overwrite all existing calculated distances. + False will only update NaNs. + inplace: updates self.links_df + + Returns: + links_df with updated distance + + """ + if units not in ["miles","meters"]: + raise NotImplementedError + + if links_df is None: + links_df = self.links_df.copy() + + msg = "Update distance in {} to variable: {}".format(units, network_variable) + if overwrite: msg + "\n - overwriting existing calculated values if found." + if use_shapes: msg + "\n - using shapes_df length if found." + WranglerLogger.debug(msg) + + """ + Start actual process + """ + + temp_links_gdf = links_df.copy() + temp_links_gdf.crs = "EPSG:4326" + temp_links_gdf = temp_links_gdf.to_crs(epsg=26915) #in meters + + conversion_from_meters = {"miles": 1/1609.34, "meters": 1} + temp_links_gdf[network_variable] = temp_links_gdf.geometry.length * conversion_from_meters[units] + + if use_shapes: + _needed_shapes_gdf = self.shapes_df.loc[ + self.shapes_df[self.shape_foreign_key] in links_df[self.shape_foreign_key] + ].copy() + + _needed_shapes_gdf = _needed_shapes_gdf.to_crs(epsg=26915) + _needed_shapes_gdf[network_variable] = _needed_shapes_gdf.geometry.length * conversion_from_meters[units] + + temp_links_gdf = update_df( + temp_links_gdf, + _needed_shapes_gdf, + merge_key = self.shape_foreign_key, + update_fields = [network_variable], + method = "update if found", + ) + + if overwrite: + links_df[network_variable] = temp_links_gdf[network_variable] + else: + links_df = update_df( + links_df, + temp_links_gdf, + merge_key = self.unique_link_key, + update_fields = [network_variable], + method = "update nan", + ) + + if inplace: + self.links_df = links_df + else: + return links_df
+ + + +
[docs] def create_managed_lane_network( + self, + keep_same_attributes_ml_and_gp: list = None, + keep_additional_attributes_ml_and_gp: list = [], + managed_lanes_required_attributes: list = [], + managed_lanes_node_id_scalar: int = None, + managed_lanes_link_id_scalar: int = None, + in_place: bool = False, + ) -> RoadwayNetwork: + """ + Create a roadway network with managed lanes links separated out. + Add new parallel managed lane links, access/egress links, + and add shapes corresponding to the new links + + args: + keep_same_attributes_ml_and_gp: list of attributes to copy from general purpose + lane to managed lane. If not specified, will look for value in the RoadwayNetwork + instance. If not found there, will default to KEEP_SAME_ATTRIBUTES_ML_AND_GP. + keep_additional_attributes_ml_and_gp: list of additional attributes to add. This is useful + if you want to leave the default attributes and then ALSO some others. + managed_lanes_required_attributes: list of attributes that are required to be specified + in new managed lanes. If not specified, will look for value in the RoadwayNetwork + instance. If not found there, will default to MANAGED_LANES_REQUIRED_ATTRIBUTES. + managed_lanes_node_id_scalar: integer value added to original node IDs to create managed + lane unique ids. If not specified, will look for value in the RoadwayNetwork + instance. If not found there, will default to MANAGED_LANES_NODE_ID_SCALAR. + managed_lanes_link_id_scalar: integer value added to original link IDs to create managed + lane unique ids. If not specified, will look for value in the RoadwayNetwork + instance. If not found there, will default to MANAGED_LANES_LINK_ID_SCALAR. + in_place: update self or return a new roadway network object + + returns: A RoadwayNetwork instance + + .. todo:: make this a more rigorous test + """ + + WranglerLogger.info("Creating network with duplicated managed lanes") + + if "ml_access" in self.links_df["roadway"].tolist(): + msg = "managed lane access links already exist in network; shouldn't be running create managed lane network. Returning network as-is." + WranglerLogger.error(msg) + if in_place: + return + else: + return copy.deepcopy(self) + + # identify parameters to use + if not keep_same_attributes_ml_and_gp: + keep_same_attributes_ml_and_gp = self.__dict__.get("keep_same_attributes_ml_and_gp") + if not keep_same_attributes_ml_and_gp: + keep_same_attributes_ml_and_gp = KEEP_SAME_ATTRIBUTES_ML_AND_GP + + if not managed_lanes_required_attributes: + managed_lanes_required_attributes = self.__dict__.get("managed_lanes_required_attributes") + if not managed_lanes_required_attributes: + managed_lanes_required_attributes = MANAGED_LANES_REQUIRED_ATTRIBUTES + + if not managed_lanes_node_id_scalar: + managed_lanes_node_id_scalar = self.__dict__.get("managed_lanes_node_id_scalar") + if not managed_lanes_node_id_scalar: + managed_lanes_node_id_scalar = MANAGED_LANES_NODE_ID_SCALAR + + if not managed_lanes_link_id_scalar: + managed_lanes_link_id_scalar = self.__dict__.get("managed_lanes_link_id_scalar") + if not managed_lanes_link_id_scalar: + managed_lanes_link_id_scalar = MANAGED_LANES_LINK_ID_SCALAR + + keep_same_attributes_ml_and_gp = list( + set(keep_same_attributes_ml_and_gp + keep_additional_attributes_ml_and_gp) + ) + + link_attributes = self.links_df.columns.values.tolist() + + ml_attributes = [i for i in link_attributes if i.startswith("ML_")] + non_ml_attributes = [i for i in link_attributes if not i.startswith("ML_")] + + # arrange the attribute list so that ML attributes will be applied at last in the for loop + # and does not get overwritten to empty string + link_attributes_ordered = non_ml_attributes + ml_attributes + + # non_ml_links are links in the network where there is no managed lane. + # gp_links are the gp lanes and ml_links are ml lanes respectively for the ML roadways. + + non_ml_links_df = self.links_df[self.links_df["managed"] != 1].copy() + non_ml_links_df = non_ml_links_df.drop(ml_attributes, axis=1) + + ml_links_df = self.links_df[self.links_df["managed"] == 1].copy() + gp_links_df = ml_links_df.drop(ml_attributes, axis=1) + + for attr in link_attributes_ordered: + if attr == "name": + ml_links_df["name"] = "Managed Lane " + gp_links_df["name"] + elif attr in ml_attributes and attr not in ["ML_ACCESS", "ML_EGRESS"]: + gp_attr = attr.split("_", 1)[1] + ml_links_df.loc[:, gp_attr] = ml_links_df[attr] + + if ( + attr not in keep_same_attributes_ml_and_gp + and attr not in managed_lanes_required_attributes + ): + ml_links_df[attr] = "" + + ml_links_df = ml_links_df.drop(ml_attributes, axis=1) + + ml_links_df["managed"] = 1 + gp_links_df["managed"] = 0 + + ml_links_df["A"] = ml_links_df["A"] + managed_lanes_node_id_scalar + ml_links_df["B"] = ml_links_df["B"] + managed_lanes_node_id_scalar + ml_links_df[self.unique_link_key] = ( + ml_links_df[self.unique_link_key] + managed_lanes_link_id_scalar + ) + ml_links_df["locationReferences"] = ml_links_df["locationReferences"].apply( + # lambda x: _update_location_reference(x) + lambda x: offset_location_reference(x) + ) + ml_links_df["geometry"] = ml_links_df["locationReferences"].apply( + lambda x: create_line_string(x) + ) + ml_links_df[self.shape_foreign_key] = ml_links_df["geometry"].apply( + lambda x: create_unique_shape_id(x) + ) + + access_links_df, egress_links_df = RoadwayNetwork.create_dummy_connector_links( + gp_links_df, ml_links_df + ) + access_links_df["geometry"] = access_links_df["locationReferences"].apply( + lambda x: create_line_string(x) + ) + egress_links_df["geometry"] = egress_links_df["locationReferences"].apply( + lambda x: create_line_string(x) + ) + access_links_df[self.shape_foreign_key] = access_links_df["geometry"].apply( + lambda x: create_unique_shape_id(x) + ) + egress_links_df[self.shape_foreign_key] = egress_links_df["geometry"].apply( + lambda x: create_unique_shape_id(x) + ) + + out_links_df = gp_links_df.append(ml_links_df) + out_links_df = out_links_df.append(access_links_df) + out_links_df = out_links_df.append(egress_links_df) + out_links_df = out_links_df.append(non_ml_links_df) + + # drop the duplicate links, if Any + # could happen when a new egress/access link gets created which already exist + out_links_df = out_links_df.drop_duplicates( + subset=["A", "B"], + keep="last" + ) + + # only the ml_links_df could potenitally have the new added nodes + + out_links_df = out_links_df.drop(['access', 'egress'], axis = 1) + + # only the ml_links_df has the new nodes added + added_a_nodes = ml_links_df["A"] + added_b_nodes = ml_links_df["B"] + + out_nodes_df = self.nodes_df + + # add node if it is not already present + for a_node in added_a_nodes: + if a_node not in out_nodes_df["model_node_id"].tolist(): + out_nodes_df = out_nodes_df.append( + { + "model_node_id": a_node, + "geometry": Point( + out_links_df[out_links_df["A"] == a_node].iloc[0][ + "locationReferences" + ][0]["point"] + ), + "drive_access": 1, + }, + ignore_index=True, + ) + + for b_node in added_b_nodes: + if b_node not in out_nodes_df["model_node_id"].tolist(): + out_nodes_df = out_nodes_df.append( + { + "model_node_id": b_node, + "geometry": Point( + out_links_df[out_links_df["B"] == b_node].iloc[0][ + "locationReferences" + ][1]["point"] + ), + "drive_access": 1, + }, + ignore_index=True, + ) + + out_nodes_df["X"] = out_nodes_df["geometry"].apply(lambda g: g.x) + out_nodes_df["Y"] = out_nodes_df["geometry"].apply(lambda g: g.y) + + out_shapes_df = self.shapes_df + + # managed lanes, access and egress connectors are new geometry + new_shapes_df = pd.DataFrame( + { + "geometry": ml_links_df["geometry"] + .append(access_links_df["geometry"]) + .append(egress_links_df["geometry"]) + } + ) + new_shapes_df[self.shape_foreign_key] = new_shapes_df["geometry"].apply( + lambda x: create_unique_shape_id(x) + ) + + out_shapes_df = out_shapes_df.append(new_shapes_df) + out_shapes_df = out_shapes_df.drop_duplicates( + subset=self.shape_foreign_key, + keep="first" + ) + + out_links_df = out_links_df.reset_index() + out_nodes_df = out_nodes_df.reset_index() + out_shapes_df = out_shapes_df.reset_index() + + if in_place: + self.links_df = out_links_df + self.nodes_df = out_nodes_df + self.shapes_df = out_shapes_df + else: + out_network = copy.deepcopy(self) + out_network.links_df = out_links_df + out_network.nodes_df = out_nodes_df + out_network.shapes_df = out_shapes_df + return out_network
+ + + +
[docs] @staticmethod + def get_modal_graph( + links_df: DataFrame, + nodes_df: DataFrame, + mode: str = None, + modes_to_network_link_variables: dict = MODES_TO_NETWORK_LINK_VARIABLES, + ): + """Determines if the network graph is "strongly" connected + A graph is strongly connected if each vertex is reachable from every other vertex. + + Args: + links_df: DataFrame of standard network links + nodes_df: DataFrame of standard network nodes + mode: mode of the network, one of `drive`,`transit`, + `walk`, `bike` + modes_to_network_link_variables: dictionary mapping the mode selections to the network variables + that must bool to true to select that mode. Defaults to MODES_TO_NETWORK_LINK_VARIABLES + + Returns: networkx: osmnx: DiGraph of network + """ + if mode not in modes_to_network_link_variables.keys(): + msg = "mode value should be one of {}.".format( + list(modes_to_network_link_variables.keys()) + ) + WranglerLogger.error(msg) + raise ValueError(msg) + + _links_df, _nodes_df = RoadwayNetwork.get_modal_links_nodes( + links_df, nodes_df, modes=[mode], + ) + G = RoadwayNetwork.ox_graph(_nodes_df, _links_df) + + return G
+ +
[docs] def is_network_connected( + self, mode: str = None, links_df: DataFrame = None, nodes_df: DataFrame = None + ): + """ + Determines if the network graph is "strongly" connected + A graph is strongly connected if each vertex is reachable from every other vertex. + + Args: + mode: mode of the network, one of `drive`,`transit`, + `walk`, `bike` + links_df: DataFrame of standard network links + nodes_df: DataFrame of standard network nodes + + Returns: boolean + + .. todo:: Consider caching graphs if they take a long time. + """ + + _nodes_df = nodes_df if nodes_df else self.nodes_df + _links_df = links_df if links_df else self.links_df + + if mode: + _links_df, _nodes_df = RoadwayNetwork.get_modal_links_nodes( + _links_df, _nodes_df, modes=[mode], + ) + else: + WranglerLogger.info( + "Assessing connectivity without a mode\ + specified. This may have limited value in interpretation.\ + To add mode specificity, add the keyword `mode =` to calling\ + this method" + ) + + # TODO: consider caching graphs if they start to take forever + # and we are calling them more than once. + G = RoadwayNetwork.ox_graph(_nodes_df, _links_df) + is_connected = nx.is_strongly_connected(G) + + return is_connected
+ + + +
[docs] def identify_segment_endpoints( + self, + mode: str = "", + links_df: DataFrame = None, + nodes_df: DataFrame = None, + min_connecting_links: int = 10, + min_distance: float = None, + max_link_deviation: int = 2, + ): + """ + + Args: + mode: list of modes of the network, one of `drive`,`transit`, + `walk`, `bike` + links_df: if specified, will assess connectivity of this + links list rather than self.links_df + nodes_df: if specified, will assess connectivity of this + nodes list rather than self.nodes_df + + """ + SEGMENT_IDENTIFIERS = ["name", "ref"] + + NAME_PER_NODE = 4 + REF_PER_NODE = 2 + + _nodes_df = nodes_df if nodes_df else self.nodes_df + _links_df = links_df if links_df else self.links_df + + if mode: + _links_df, _nodes_df = RoadwayNetwork.get_modal_links_nodes( + _links_df, _nodes_df, modes=[mode], + ) + else: + WranglerLogger.warning( + "Assessing connectivity without a mode\ + specified. This may have limited value in interpretation.\ + To add mode specificity, add the keyword `mode =` to calling\ + this method" + ) + + _nodes_df = RoadwayNetwork.add_incident_link_data_to_nodes( + links_df=_links_df, + nodes_df=_nodes_df, + link_variables=SEGMENT_IDENTIFIERS + ["distance"], + ) + WranglerLogger.debug("Node/Link table elements: {}".format(len(_nodes_df))) + + # Screen out segments that have blank name AND refs + _nodes_df = _nodes_df.replace(r"^\s*$", np.nan, regex=True).dropna( + subset=["name", "ref"] + ) + + WranglerLogger.debug( + "Node/Link table elements after dropping empty name AND ref : {}".format( + len(_nodes_df) + ) + ) + + # Screen out segments that aren't likely to be long enough + # Minus 1 in case ref or name is missing on an intermediate link + _min_ref_in_table = REF_PER_NODE * (min_connecting_links - max_link_deviation) + _min_name_in_table = NAME_PER_NODE * (min_connecting_links - max_link_deviation) + + _nodes_df["ref_freq"] = _nodes_df["ref"].map(_nodes_df["ref"].value_counts()) + _nodes_df["name_freq"] = _nodes_df["name"].map(_nodes_df["name"].value_counts()) + + _nodes_df = _nodes_df.loc[ + (_nodes_df["ref_freq"] >= _min_ref_in_table) + & (_nodes_df["name_freq"] >= _min_name_in_table) + ] + + WranglerLogger.debug( + "Node/Link table has n = {} after screening segments for min length:\n{}".format( + len(_nodes_df), + _nodes_df[ + [ + self.unique_node_key, + "name", + "ref", + "distance", + "ref_freq", + "name_freq", + ] + ], + ) + ) + # ---------------------------------------- + # Find nodes that are likely endpoints + # ---------------------------------------- + + # - Likely have one incident link and one outgoing link + _max_ref_endpoints = REF_PER_NODE / 2 + _max_name_endpoints = NAME_PER_NODE / 2 + # - Attach frequency of node/ref + _nodes_df = _nodes_df.merge( + _nodes_df.groupby(by=[self.unique_node_key, "ref"]) + .size() + .rename("ref_N_freq"), + on=[self.unique_node_key, "ref"], + ) + # WranglerLogger.debug("_ref_count+_nodes:\n{}".format(_nodes_df[["model_node_id","ref","name","ref_N_freq"]])) + # - Attach frequency of node/name + _nodes_df = _nodes_df.merge( + _nodes_df.groupby(by=[self.unique_node_key, "name"]) + .size() + .rename("name_N_freq"), + on=[self.unique_node_key, "name"], + ) + # WranglerLogger.debug("_name_count+_nodes:\n{}".format(_nodes_df[["model_node_id","ref","name","name_N_freq"]])) + + WranglerLogger.debug( + "Possible segment endpoints:\n{}".format( + _nodes_df[ + [ + self.unique_node_key, + "name", + "ref", + "distance", + "ref_N_freq", + "name_N_freq", + ] + ] + ) + ) + # - Filter possible endpoint list based on node/name node/ref frequency + _nodes_df = _nodes_df.loc[ + (_nodes_df["ref_N_freq"] <= _max_ref_endpoints) + | (_nodes_df["name_N_freq"] <= _max_name_endpoints) + ] + WranglerLogger.debug( + "{} Likely segment endpoints with req_ref<= {} or freq_name<={} \n{}".format( + len(_nodes_df), + _max_ref_endpoints, + _max_name_endpoints, + _nodes_df[ + [ + self.unique_node_key, + "name", + "ref", + "ref_N_freq", + "name_N_freq", + ] + ], + ) + ) + # ---------------------------------------- + # Assign a segment id + # ---------------------------------------- + _nodes_df["segment_id"], _segments = pd.factorize( + _nodes_df.name + _nodes_df.ref + ) + WranglerLogger.debug("{} Segments:\n{}".format(len(_segments), _segments)) + + # ---------------------------------------- + # Drop segments without at least two nodes + # ---------------------------------------- + + # https://stackoverflow.com/questions/13446480/python-pandas-remove-entries-based-on-the-number-of-occurrences + _nodes_df = _nodes_df[ + _nodes_df.groupby(["segment_id", self.unique_node_key])[ + self.unique_node_key + ].transform(len) + > 1 + ] + + WranglerLogger.debug( + "{} Segments with at least nodes:\n{}".format( + len(_nodes_df), + _nodes_df[ + [self.unique_node_key, "name", "ref", "segment_id"] + ], + ) + ) + + # ---------------------------------------- + # For segments with more than two nodes, find farthest apart pairs + # ---------------------------------------- + + def _max_segment_distance(row): + _segment_nodes = _nodes_df.loc[_nodes_df["segment_id"] == row["segment_id"]] + dist = _segment_nodes.geometry.distance(row.geometry) + return max(dist.dropna()) + + _nodes_df["seg_distance"] = _nodes_df.apply(_max_segment_distance, axis=1) + _nodes_df = _nodes_df.merge( + _nodes_df.groupby("segment_id") + .seg_distance.agg(max) + .rename("max_seg_distance"), + on="segment_id", + ) + + _nodes_df = _nodes_df.loc[ + (_nodes_df["max_seg_distance"] == _nodes_df["seg_distance"]) + & (_nodes_df["seg_distance"] > 0) + ].drop_duplicates(subset=[self.unique_node_key, "segment_id"]) + + # ---------------------------------------- + # Reassign segment id for final segments + # ---------------------------------------- + _nodes_df["segment_id"], _segments = pd.factorize( + _nodes_df.name + _nodes_df.ref + ) + + WranglerLogger.debug( + "{} Segments:\n{}".format( + len(_segments), + _nodes_df[ + [ + self.unique_node_key, + "name", + "ref", + "segment_id", + "seg_distance", + ] + ], + ) + ) + + return _nodes_df[ + ["segment_id", self.unique_node_key, "geometry", "name", "ref"] + ]
+ +
[docs] def identify_segment( + self, + O_id, + D_id, + selection_dict: dict = {}, + mode=None, + nodes_df=None, + links_df=None, + ): + """ + Args: + endpoints: list of length of two unique keys of nodes making up endpoints of segment + selection_dict: dictionary of link variables to select candidate links from, otherwise will create a graph of ALL links which will be both a RAM hog and could result in odd shortest paths. + segment_variables: list of variables to keep + """ + _nodes_df = nodes_df if nodes_df else self.nodes_df + _links_df = links_df if links_df else self.links_df + + if mode: + _links_df, _nodes_df = RoadwayNetwork.get_modal_links_nodes( + _links_df, _nodes_df, modes=[mode], + ) + else: + WranglerLogger.warning( + "Assessing connectivity without a mode\ + specified. This may have limited value in interpretation.\ + To add mode specificity, add the keyword `mode =` to calling\ + this method" + ) + + if selection_dict: + _query = " or ".join( + [f"{k} == {repr(v)}" for k, v in selection_dict.items()] + ) + _candidate_links = _links_df.query(_query) + WranglerLogger.debug( + "Found {} candidate links from {} total links using following query:\n{}".format( + len(_candidate_links), len(_links_df), _query + ) + ) + else: + _candidate_links = _links_df + + WranglerLogger.warning( + "Not pre-selecting links using selection_dict can use up a lot of RAM and also result in odd segment paths." + ) + + WranglerLogger.debug( + "_candidate links for segment: \n{}".format( + _candidate_links[["u", "v", "A", "B", "name", "ref"]] + ) + ) + + try: + _sp = False + (G, candidate_links, sp_route, sp_links) = self.path_search( + _candidate_links, O_id, D_id + ) + _sp = True + except NoPathFound: + msg = "Route not found from {} to {} using selection candidates {}".format( + O_id, D_id, selection_dict + ) + WranglerLogger.warning(msg) + sp_links = pd.DataFrame() + + return sp_links
+ +
[docs] def assess_connectivity( + self, + mode: str = "", + ignore_end_nodes: bool = True, + links_df: DataFrame = None, + nodes_df: DataFrame = None, + ): + """Returns a network graph and list of disconnected subgraphs + as described by a list of their member nodes. + + Args: + mode: list of modes of the network, one of `drive`,`transit`, + `walk`, `bike` + ignore_end_nodes: if True, ignores stray singleton nodes + links_df: if specified, will assess connectivity of this + links list rather than self.links_df + nodes_df: if specified, will assess connectivity of this + nodes list rather than self.nodes_df + + Returns: Tuple of + Network Graph (osmnx flavored networkX DiGraph) + List of disconnected subgraphs described by the list of their + member nodes (as described by their `model_node_id`) + """ + _nodes_df = nodes_df if nodes_df else self.nodes_df + _links_df = links_df if links_df else self.links_df + + if mode: + _links_df, _nodes_df = RoadwayNetwork.get_modal_links_nodes( + _links_df, _nodes_df, modes=[mode], + ) + else: + WranglerLogger.warning( + "Assessing connectivity without a mode\ + specified. This may have limited value in interpretation.\ + To add mode specificity, add the keyword `mode =` to calling\ + this method" + ) + + G = RoadwayNetwork.ox_graph(_nodes_df, _links_df) + # sub_graphs = [s for s in sorted(nx.strongly_connected_component_subgraphs(G), key=len, reverse=True)] + sub_graphs = [ + s + for s in sorted( + (G.subgraph(c) for c in nx.strongly_connected_components(G)), + key=len, + reverse=True, + ) + ] + + sub_graph_nodes = [ + list(s) + for s in sorted(nx.strongly_connected_components(G), key=len, reverse=True) + ] + + # sorted on decreasing length, dropping the main sub-graph + disconnected_sub_graph_nodes = sub_graph_nodes[1:] + + # dropping the sub-graphs with only 1 node + if ignore_end_nodes: + disconnected_sub_graph_nodes = [ + list(s) for s in disconnected_sub_graph_nodes if len(s) > 1 + ] + + WranglerLogger.info( + "{} for disconnected networks for mode = {}:\n{}".format( + self.node_foreign_key, + mode, + "\n".join(list(map(str, disconnected_sub_graph_nodes))), + ) + ) + return G, disconnected_sub_graph_nodes
+ +
[docs] @staticmethod + def network_connection_plot(G, disconnected_subgraph_nodes: list): + """Plot a graph to check for network connection. + + Args: + G: OSMNX flavored networkX graph. + disconnected_subgraph_nodes: List of disconnected subgraphs described by the list of their + member nodes (as described by their `model_node_id`). + + returns: fig, ax : tuple + """ + + colors = [] + for i in range(len(disconnected_subgraph_nodes)): + colors.append("#%06X" % randint(0, 0xFFFFFF)) + + fig, ax = ox.plot_graph( + G, + figsize=(16, 16), + show=False, + close=True, + edge_color="black", + edge_alpha=0.1, + node_color="black", + node_alpha=0.5, + node_size=10, + ) + i = 0 + for nodes in disconnected_subgraph_nodes: + for n in nodes: + size = 100 + ax.scatter(G.nodes[n]["X"], G.nodes[n]["Y"], c=colors[i], s=size) + i = i + 1 + + return fig, ax
+ +
[docs] def selection_map( + self, + selected_link_idx: list, + A: Optional[Any] = None, + B: Optional[Any] = None, + candidate_link_idx: Optional[List] = [], + ): + """ + Shows which links are selected for roadway property change or parallel + managed lanes category of roadway projects. + + Args: + selected_links_idx: list of selected link indices + candidate_links_idx: optional list of candidate link indices to also include in map + A: optional foreign key of starting node of a route selection + B: optional foreign key of ending node of a route selection + """ + WranglerLogger.debug( + "Selected Links: {}\nCandidate Links: {}\n".format( + selected_link_idx, candidate_link_idx + ) + ) + + graph_link_idx = list(set(selected_link_idx + candidate_link_idx)) + graph_links = self.links_df.loc[graph_link_idx] + + node_list_foreign_keys = list( + set( + [ + i + for fk in self.link_foreign_key + for i in list(graph_links[fk]) + ] + ) + ) + graph_nodes = self.nodes_df.loc[self.nodes_df[self.node_foreign_key].isin(node_list_foreign_keys)] + + G = RoadwayNetwork.ox_graph(graph_nodes, graph_links) + + # base map plot with whole graph + m = ox.plot_graph_folium( + G, edge_color=None, tiles="cartodbpositron", width="300px", height="250px" + ) + + # plot selection + selected_links = self.links_df.loc[selected_link_idx] + + for _, row in selected_links.iterrows(): + pl = ox.folium._make_folium_polyline( + edge=row, edge_color="blue", edge_width=5, edge_opacity=0.8 + ) + pl.add_to(m) + + # if have A and B node add them to base map + def _folium_node(node_row, color="white", icon=""): + node_marker = folium.Marker( + location=[node_row["Y"], node_row["X"]], + icon=folium.Icon(icon=icon, color=color), + ) + return node_marker + + if A: + + # WranglerLogger.debug("A: {}\n{}".format(A,self.nodes_df[self.nodes_df[RoadwayNetwork.NODE_FOREIGN_KEY] == A])) + _folium_node( + self.nodes_df[self.nodes_df[self.node_foreign_key] == A], + color="green", + icon="play", + ).add_to(m) + + if B: + _folium_node( + self.nodes_df[self.nodes_df[self.node_foreign_key] == B], + color="red", + icon="star", + ).add_to(m) + + return m
+ +
[docs] def deletion_map(self, links: dict, nodes: dict): + """ + Shows which links and nodes are deleted from the roadway network + """ + # deleted_links = None + # deleted_nodes = None + + missing_error_message = [] + + if links is not None: + for key, val in links.items(): + deleted_links = self.links_df[self.links_df[key].isin(val)] + + node_list_foreign_keys = list( + set( + [ + i + for fk in self.link_foreign_key + for i in list(deleted_links[fk]) + ] + ) + ) + candidate_nodes = self.nodes_df.loc[node_list_foreign_keys] + else: + deleted_links = None + + if nodes is not None: + for key, val in nodes.items(): + deleted_nodes = self.nodes_df[self.nodes_df[key].isin(val)] + else: + deleted_nodes = None + + G = RoadwayNetwork.ox_graph(candidate_nodes, deleted_links) + + m = ox.plot_graph_folium(G, edge_color="red", tiles="cartodbpositron") + + def _folium_node(node, color="white", icon=""): + node_circle = folium.Circle( + location=[node["Y"], node["X"]], + radius=2, + fill=True, + color=color, + fill_opacity=0.8, + ) + return node_circle + + if deleted_nodes is not None: + for _, row in deleted_nodes.iterrows(): + _folium_node(row, color="red").add_to(m) + + return m
+ +
[docs] def addition_map(self, links: dict, nodes: dict): + """ + Shows which links and nodes are added to the roadway network + """ + + if links is not None: + link_ids = [] + for link in links: + link_ids.append(link.get(self.unique_link_key)) + + added_links = self.links_df[ + self.links_df[self.unique_link_key].isin(link_ids) + ] + node_list_foreign_keys = list( + set( + [ + i + for fk in self.link_foreign_key + for i in list(added_links[fk]) + ] + ) + ) + try: + candidate_nodes = self.nodes_df.loc[node_list_foreign_keys] + except: + return None + + if nodes is not None: + node_ids = [] + for node in nodes: + node_ids.append(node.get(self.unique_node_key)) + + added_nodes = self.nodes_df[ + self.nodes_df[self.unique_node_key].isin(node_ids) + ] + else: + added_nodes = None + + G = RoadwayNetwork.ox_graph(candidate_nodes, added_links) + + m = ox.plot_graph_folium(G, edge_color="green", tiles="cartodbpositron") + + def _folium_node(node, color="white", icon=""): + node_circle = folium.Circle( + location=[node["Y"], node["X"]], + radius=2, + fill=True, + color=color, + fill_opacity=0.8, + ) + return node_circle + + if added_nodes is not None: + for _, row in added_nodes.iterrows(): + _folium_node(row, color="green").add_to(m) + + return m
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/network_wrangler/scenario/index.html b/_modules/network_wrangler/scenario/index.html new file mode 100644 index 00000000..7af4c211 --- /dev/null +++ b/_modules/network_wrangler/scenario/index.html @@ -0,0 +1,888 @@ + + + + + + network_wrangler.scenario — Network Wrangler documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for network_wrangler.scenario

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from __future__ import annotations
+import os
+import sys
+import glob
+import copy
+import pprint
+from datetime import datetime
+from typing import Union
+
+import pandas as pd
+import geopandas as gpd
+
+from .projectcard import ProjectCard
+from collections import OrderedDict
+from .logger import WranglerLogger
+from collections import defaultdict
+from .utils import topological_sort
+from .roadwaynetwork import RoadwayNetwork
+from .transitnetwork import TransitNetwork
+
+
+
[docs]class Scenario(object): + """ + Holds information about a scenario. + + .. highlight:: python + + Typical usage example: + :: + my_base_scenario = { + "road_net": RoadwayNetwork.read( + link_filename=STPAUL_LINK_FILE, + node_filename=STPAUL_NODE_FILE, + shape_filename=STPAUL_SHAPE_FILE, + fast=True, + ), + "transit_net": TransitNetwork.read(STPAUL_DIR), + } + + card_filenames = [ + "3_multiple_roadway_attribute_change.yml", + "multiple_changes.yml", + "4_simple_managed_lane.yml", + ] + + project_card_directory = os.path.join(STPAUL_DIR, "project_cards") + + project_cards_list = [ + ProjectCard.read(os.path.join(project_card_directory, filename), validate=False) + for filename in card_filenames + ] + + my_scenario = Scenario.create_scenario( + base_scenario=my_base_scenario, + project_cards_list=project_cards_list, + ) + my_scenario.check_scenario_requisites() + + my_scenario.apply_all_projects() + + my_scenario.scenario_summary() + + Attributes: + base_scenario: dictionary representation of a scenario + project_cards (Optional): list of Project Card Instances + road_net: instance of RoadwayNetwork for the scenario + transit_net: instance of TransitNetwork for the scenario + applied_projects: list of project names that have been applied + project_cards: list of project card instances + ordered_project_cards: + prerequisites: dictionary storing prerequiste information + corequisites: dictionary storing corequisite information + conflicts: dictionary storing conflict information + requisites_checked: boolean indicating if the co- and pre-requisites + have been checked in the project cards + conflicts_checked: boolean indicating if the project conflicts have been checked + has_requisite_error: boolean indicating if there is a conflict in the pre- or + co-requisites of project cards + has_conflict_error: boolean indicating if there is are conflicting project cards + prerequisites_sorted: boolean indicating if the project cards have + been sorted to make sure cards that are pre-requisites are applied first + """ + +
[docs] def __init__(self, base_scenario: dict, project_cards: [ProjectCard] = None): + """ + Constructor + + args: + base_scenario: dict the base scenario + project_cards: list this scenario's project cards + """ + + self.road_net = None + self.transit_net = None + + if type(base_scenario) is dict: + pass + elif isinstance(base_scenario, Scenario): + base_scenario = base_scenario.__dict__ + else: + msg = "Base scenario should be a dict or instance of Scenario: found {} which is of type:{}".format( + base_scenario, type(base_scenario) + ) + WranglerLogger.error(msg) + raise ValueError(msg) + + self.base_scenario = base_scenario + + # if the base scenario had roadway or transit networks, use them as the basis. + if self.base_scenario.get("road_net"): + self.road_net = copy.deepcopy(self.base_scenario["road_net"]) + if self.base_scenario.get("transit_net"): + self.transit_net = copy.deepcopy(self.base_scenario["transit_net"]) + + # if the base scenario had applied projects, add them to the list of applied + self.applied_projects = [] + if self.base_scenario.get("applied_projects"): + self.applied_projects = base_scenario["applied_projects"] + + self.project_cards = project_cards + self.ordered_project_cards = OrderedDict() + + self.prerequisites = {} + self.corequisites = {} + self.conflicts = {} + + self.requisites_checked = False + self.conflicts_checked = False + + self.has_requisite_error = False + self.has_conflict_error = False + + self.prerequisites_sorted = False + + for card in self.project_cards: + if not card.__dict__.get("dependencies"): + continue + + if card.dependencies.get("prerequisites"): + self.prerequisites.update( + {card.project: card.dependencies["prerequisites"]} + ) + if card.dependencies.get("corequisites"): + self.corequisites.update( + {card.project: card.dependencies["corequisites"]} + )
+ +
[docs] @staticmethod + def create_base_scenario( + base_shape_name: str, + base_link_name: str, + base_node_name: str, + roadway_dir: str = "", + transit_dir: str = "", + validate: bool = True, + **kwargs, + ) -> Scenario: + """ + args + ----- + roadway_dir: optional + path to the base scenario roadway network files + base_shape_name: + filename of the base network shape + base_link_name: + filename of the base network link + base_node_name: + filename of the base network node + transit_dir: optional + path to base scenario transit files + validate: + boolean indicating whether to validate the base network or not + """ + if roadway_dir: + base_network_shape_file = os.path.join(roadway_dir, base_shape_name) + base_network_link_file = os.path.join(roadway_dir, base_link_name) + base_network_node_file = os.path.join(roadway_dir, base_node_name) + else: + base_network_shape_file = base_shape_name + base_network_link_file = base_link_name + base_network_node_file = base_node_name + + road_net = RoadwayNetwork.read( + link_filename=base_network_link_file, + node_filename=base_network_node_file, + shape_filename=base_network_shape_file, + fast=not validate, + **kwargs, + ) + + if transit_dir: + transit_net = TransitNetwork.read(transit_dir) + else: + transit_net = None + WranglerLogger.info( + "No transit directory specified, base scenario will have empty transit network." + ) + + transit_net.set_roadnet(road_net, validate_consistency=validate) + base_scenario = {"road_net": road_net, "transit_net": transit_net} + + return base_scenario
+ +
[docs] @staticmethod + def create_scenario( + base_scenario: dict = {}, + card_directory: str = "", + tags: [str] = None, + project_cards_list=[], + glob_search="", + validate_project_cards=True, + ) -> Scenario: + """ + Validates project cards with a specific tag from the specified folder or + list of user specified project cards and + creates a scenario object with the valid project card. + + args + ----- + base_scenario: + object dictionary for the base scenario (i.e. my_base_scenario.__dict__) + tags: + only project cards with these tags will be read/validated + folder: + the folder location where the project cards will be + project_cards_list: + list of project cards to be applied + glob_search: + + """ + WranglerLogger.info("Creating Scenario") + + if project_cards_list: + WranglerLogger.debug( + "Adding project cards from List.\n{}".format( + ",".join([p.project for p in project_cards_list]) + ) + ) + scenario = Scenario(base_scenario, project_cards=project_cards_list) + + if card_directory and tags: + WranglerLogger.debug( + "Adding project cards from directory and tags.\nDir: {}\nTags: {}".format( + card_directory, ",".join(tags) + ) + ) + scenario.add_project_cards_from_tags( + card_directory, + tags=tags, + glob_search=glob_search, + validate=validate_project_cards, + ) + elif card_directory: + WranglerLogger.debug( + "Adding project cards from directory.\nDir: {}".format(card_directory) + ) + scenario.add_project_cards_from_directory( + card_directory, glob_search=glob_search, validate=validate_project_cards + ) + return scenario
+ +
[docs] def add_project_card_from_file( + self, project_card_filename: str, validate: bool = True, tags: list = [] + ): + + WranglerLogger.debug( + "Trying to add project card from file: {}".format(project_card_filename) + ) + project_card = ProjectCard.read(project_card_filename, validate=validate) + + if project_card == None: + msg = "project card not read: {}".format(project_card_filename) + WranglerLogger.error(msg) + raise ValueError(msg) + + if tags and set(tags).isdisjoint(project_card.tags): + WranglerLogger.debug( + "Project card tags: {} don't match search tags: {}".format( + ",".join(project_card.tags), ",".join(tags) + ) + ) + return + + if project_card.project in self.get_project_names(): + msg = "project card with name '{}' already in Scenario. Project names must be unique".format( + project_card.project + ) + WranglerLogger.error(msg) + raise ValueError(msg) + + self.requisites_checked = False + self.conflicts_checked = False + self.prerequisites_sorted = False + + WranglerLogger.debug( + "Adding project card to scenario: {}".format(project_card.project) + ) + self.project_cards.append(project_card) + + if not project_card.__dict__.get("dependencies"): + return + + WranglerLogger.debug("Adding project card dependencies") + if project_card.dependencies.get("prerequisites"): + self.prerequisites.update( + {project_card.project: project_card.dependencies["prerequisites"]} + ) + if project_card.dependencies.get("corequisites"): + self.corequisites.update( + {project_card.project: project_card.dependencies["corequisites"]} + ) + if project_card.dependencies.get("conflicts"): + self.conflicts.update( + {project_card.project: project_card.dependencies["conflicts"]} + )
+ +
[docs] def add_project_cards_from_directory( + self, folder: str, glob_search="", validate=True + ): + """ + Adds projects cards to the scenario. + A folder is provided to look for project cards and if applicable, a glob-style search. + + i.e. glob_search = 'road*.yml' + + args: + folder: the folder location where the project cards will be + glob_search: https://docs.python.org/2/library/glob.html + """ + + if not os.path.exists(folder): + msg = "Cannot find specified directory to add project cards: {}".format( + folder + ) + WranglerLogger.error(msg) + raise ValueError(msg) + + if glob_search: + WranglerLogger.info( + "Adding project cards using glob search: {}".format(glob_search) + ) + for file in glob.iglob(os.path.join(folder, glob_search)): + if not (file.endswith(".yml") or file.endswith(".yaml") or file.endswith(".wrangler") or file.endswith(".wr")): + continue + else: + self.add_project_card_from_file(file, validate=validate) + else: + for file in os.listdir(folder): + if not (file.endswith(".yml") or file.endswith(".yaml") or file.endswith(".wrangler") or file.endswith(".wr")): + continue + else: + self.add_project_card_from_file( + os.path.join(folder, file), validate=validate + )
+ +
[docs] def add_project_cards_from_tags( + self, folder: str, tags: [str] = [], glob_search="", validate=True + ): + """ + Adds projects cards to the scenario. + A folder is provided to look for project cards that have a matching tag that is passed to the method. + + args: + folder: the folder location where the project cards will be + tags: only project cards with these tags will be validated and added to the returning scenario + """ + + if glob_search: + WranglerLogger.debug( + "Adding project cards using \n-tags: {} and \nglob search: {}".format( + tags, glob_search + ) + ) + for file in glob.iglob(os.path.join(folder, glob_search)): + self.add_project_card_from_file(file, tags=tags, validate=validate) + else: + WranglerLogger.debug("Adding project cards using \n-tags: {}".format(tags)) + for file in os.listdir(folder): + self.add_project_card_from_file( + os.path.join(folder, file), tags=tags, validate=validate + )
+ + def __str__(self): + s = ["{}: {}".format(key, value) for key, value in self.__dict__.items()] + return "\n".join(s) + +
[docs] def get_project_names(self) -> list: + """ + Returns a list of project names + """ + return [project_card.project for project_card in self.project_cards]
+ +
[docs] def check_scenario_conflicts(self) -> bool: + """ + Checks if there are any conflicting projects in the scenario + Fail if the project A specifies that project B is a conflict and project B is included in the scenario + + Returns: boolean indicating if the check was successful or returned an error + """ + + conflict_dict = self.conflicts + scenario_projects = [p.project for p in self.project_cards] + + for project, conflicts in conflict_dict.items(): + if conflicts: + for name in conflicts: + if name in scenario_projects: + self.project_cards + WranglerLogger.error( + "Projects %s has %s as conflicting project" + % (project, name) + ) + self.has_conflict_error = True + + self.conflicts_checked = True + + return self.has_conflict_error
+ +
[docs] def check_scenario_requisites(self) -> bool: + """ + Checks if there are any missing pre- or co-requisite projects in the scenario + Fail if the project A specifies that project B is a pre- or co-requisite and project B is not included in the scenario + + Returns: boolean indicating if the checks were successful or returned an error + """ + + corequisite_dict = self.corequisites + prerequisite_dict = self.prerequisites + + scenario_projects = [p.project for p in self.project_cards] + + for project, coreq in corequisite_dict.items(): + if coreq: + for name in coreq: + if name not in scenario_projects: + WranglerLogger.error( + "Projects %s has %s as corequisite project which is missing for the scenario" + % (project, name) + ) + self.has_requisite_error = True + + for project, prereq in prerequisite_dict.items(): + if prereq: + for name in prereq: + if name not in scenario_projects: + WranglerLogger.error( + "Projects %s has %s as prerequisite project which is missing for the scenario" + % (project, name) + ) + self.has_requisite_error = True + + self.requisites_checked = True + + return self.has_requisite_error
+ +
[docs] def order_project_cards(self): + """ + create a list of project cards such that they are in order based on pre-requisites + + Returns: ordered list of project cards to be applied to scenario + """ + + scenario_projects = [p.project.lower() for p in self.project_cards] + + # build prereq (adjacency) list for topological sort + adjacency_list = defaultdict(list) + visited_list = defaultdict() + + for project in scenario_projects: + visited_list[project] = False + if not self.prerequisites.get(project): + continue + for prereq in self.prerequisites[project]: + if ( + prereq.lower() in scenario_projects + ): # this will always be true, else would have been flagged in missing prerequsite check, but just in case + adjacency_list[prereq.lower()] = [project] + + # sorted_project_names is topological sorted project card names (based on prerequsiite) + sorted_project_names = topological_sort( + adjacency_list=adjacency_list, visited_list=visited_list + ) + + # get the project card objects for these sorted project names + project_card_and_name_dict = {} + for project_card in self.project_cards: + project_card_and_name_dict[project_card.project.lower()] = project_card + + sorted_project_cards = [ + project_card_and_name_dict[project_name] + for project_name in sorted_project_names + ] + + try: + assert len(sorted_project_cards) == len(self.project_cards) + except: + msg = "Sorted project cards ({}) are not of same number as unsorted ({}).".format( + len(sorted_project_cards), len(self.project_cards) + ) + WranglerLogger.error(msg) + raise ValueError(msg) + + self.prerequisites_sorted = True + self.ordered_project_cards = { + project_name: project_card_and_name_dict[project_name] + for project_name in sorted_project_names + } + + WranglerLogger.debug( + "Ordered Project Cards: {}".format(self.ordered_project_cards) + ) + self.project_cards = sorted_project_cards + + WranglerLogger.debug("Project Cards: {}".format(self.project_cards)) + + return sorted_project_cards
+ +
[docs] def apply_all_projects(self): + + # Get everything in order + + if not self.requisites_checked: + self.check_scenario_requisites() + if not self.conflicts_checked: + self.check_scenario_conflicts() + if not self.prerequisites_sorted: + self.order_project_cards() + + for p in self.project_cards: + self.apply_project(p.__dict__)
+ +
[docs] def apply_project(self, p): + if isinstance(p, ProjectCard): + p = p.__dict__ + + if p.get("project"): + WranglerLogger.info("Applying [{}]:{}".format(p["file"], p["project"])) + + # for changes, iterate through the changes by calling this method for each change + if p.get("changes"): + part = 1 + for pc in p["changes"]: + pc["project"] = p["project"] + pc["file"] = p["file"] + self.apply_project(pc) + + else: + if p["category"] in ProjectCard.ROADWAY_CATEGORIES: + if not self.road_net: + raise ("Missing Roadway Network") + self.road_net.apply(p) + if p["category"] in ProjectCard.TRANSIT_CATEGORIES: + if not self.transit_net: + raise ("Missing Transit Network") + self.transit_net.apply(p) + if ( + p["category"] in ProjectCard.SECONDARY_TRANSIT_CATEGORIES + and self.transit_net + ): + self.transit_net.apply(p) + + if p["project"] not in self.applied_projects: + self.applied_projects.append(p["project"])
+ +
[docs] def remove_all_projects(self): + self.project_cards = []
+ +
[docs] def applied_project_card_summary(self, project_card_dictionary: dict) -> dict: + """ + Create a summary of applied project card and what they changed for the scenario. + + Args: + project_card_dictionary: dictionary representation of the values of a project card (i.e. ProjectCard.__dict__ ) + + Returns: + A dict of project summary change dictionaries for each change + """ + changes = project_card_dictionary.get("changes", [project_card_dictionary]) + + summary = { + "project_card": project_card_dictionary["file"], + "total_parts": len(changes), + } + + def _summarize_change_roadway(change: dict, change_summary: dict): + + sel_key = RoadwayNetwork.build_selection_key( + self.road_net, change["facility"] + ) + + change_summary["sel_idx"] = self.road_net.selections[sel_key][ + "selected_links" + ].index.tolist() + + change_summary["attributes"] = [p["property"] for p in change["properties"]] + + if type(sel_key) == tuple: + _, A_id, B_id = sel_key + else: + A_id, B_id = (None, None) + + change_summary["map"] = self.road_net.selection_map( + change_summary["sel_idx"], + A=A_id, + B=B_id, + candidate_link_idx=self.road_net.selections[sel_key] + .get("candidate_links", pd.DataFrame([])) + .index.tolist(), + ) + return change_summary + + def _summarize_add_roadway(change: dict, change_summary: dict): + change_summary["added_links"] = pd.DataFrame(change.get("links")) + change_summary["added_nodes"] = pd.DataFrame(change.get("nodes")) + change_summary["map"] = RoadwayNetwork.addition_map( + self.road_net, change.get("links"), change.get("nodes"), + ) + return change_summary + + def _summarize_deletion(change: dict, change_summary: dict): + change_summary["deleted_links"] = change.get("links") + change_summary["deleted_nodes"] = change.get("nodes") + change_summary["map"] = RoadwayNetwork.deletion_map( + self.base_scenario["road_net"], + change.get("links"), + change.get("nodes"), + ) + return change_summary + + for i, change in enumerate(changes): + WranglerLogger.debug( + "Summarizing {} Part: {}".format( + project_card_dictionary["project"], i + 1 + ) + ) + change_summary = { + "project": project_card_dictionary["project"] + " – Part " + str(i + 1), + "category": change["category"].lower(), + } + + if change["category"].lower() == "roadway deletion": + change_summary = _summarize_deletion(change, change_summary) + elif change["category"].lower() == "add new roadway": + change_summary = _summarize_add_roadway(change, change_summary) + elif change["category"].lower() in [ + "roadway property change", + "parallel managed lanes", + ]: + change_summary = _summarize_change_roadway(change, change_summary) + + summary["Part " + str(i + 1)] = change_summary + + return summary
+ +
[docs] def scenario_summary( + self, project_detail: bool = True, outfile: str = "", mode: str = "a" + ) -> str: + """ + A high level summary of the created scenario. + + Args: + project_detail: If True (default), will write out project card summaries. + outfile: If specified, will write scenario summary to text file. + mode: Outfile open mode. 'a' to append 'w' to overwrite. + + Returns: + string of summary + + """ + + WranglerLogger.info("Summarizing Scenario") + report_str = "------------------------------\n" + report_str += "Scenario created on {}\n".format(datetime.now()) + + report_str += "Base Scenario:\n" + report_str += "--Road Network:\n" + report_str += "----Link File: {}\n".format( + self.base_scenario["road_net"].link_filename + ) + report_str += "----Node File: {}\n".format( + self.base_scenario["road_net"].node_filename + ) + report_str += "----Shape File: {}\n".format( + self.base_scenario["road_net"].shape_filename + ) + report_str += "--Transit Network:\n" + report_str += "----Feed Path: {}\n".format( + self.base_scenario["transit_net"].feed_path + ) + + report_str += "\nProject Cards:\n -" + report_str += "\n-".join(p.file for p in self.project_cards) + + report_str += "\nApplied Projects:\n-" + report_str += "\n-".join(self.applied_projects) + + if project_detail: + report_str += "\n---Project Card Details---\n" + for p in self.project_cards: + report_str += "\n{}".format( + pprint.pformat(self.applied_project_card_summary(p.__dict__)) + ) + + if outfile: + with open(outfile, mode) as f: + f.write(report_str) + WranglerLogger.info("Wrote Scenario Report to: {}".format(outfile)) + + return report_str
+ + +def net_to_mapbox( + roadway: Union[RoadwayNetwork, gpd.GeoDataFrame] = gpd.GeoDataFrame(), + transit: Union[TransitNetwork, gpd.GeoDataFrame] = gpd.GeoDataFrame(), + roadway_geojson: str = "roadway_shapes.geojson", + transit_geojson: str = "transit_shapes.geojson", + mbtiles_out: str = "network.mbtiles", + overwrite: bool = True, + port: str = "9000", +): + """ + + Args: + roadway: a RoadwayNetwork instance or a geodataframe with roadway linetrings + transit: a TransitNetwork instance or a geodataframe with transit linetrings + """ + import subprocess + + # test for mapbox token + try: + token = os.getenv("MAPBOX_ACCESS_TOKEN") + except: + raise ( + "NEED TO SET MAPBOX ACCESS TOKEN IN ENVIRONMENT VARIABLES/n In command line: >>export MAPBOX_ACCESS_TOKEN='pk.0000.1111' # replace value with your mapbox public access token" + ) + + if type(transit) != gpd.GeoDataFrame: + transit = TransitNetwork.transit_net_to_gdf(transit) + if type(roadway) != gpd.GeoDataFrame: + roadway = RoadwayNetwork.roadway_net_to_gdf(roadway) + + transit.to_file(transit_geojson, driver="GeoJSON") + roadway.to_file(roadway_geojson, driver="GeoJSON") + + tippe_options_list = ["-zg", "-o", mbtiles_out] + if overwrite: + tippe_options_list.append("--force") + # tippe_options_list.append("--drop-densest-as-needed") + tippe_options_list.append(roadway_geojson) + tippe_options_list.append(transit_geojson) + + try: + WranglerLogger.info( + "Running tippecanoe with following options: {}".format( + " ".join(tippe_options_list) + ) + ) + tippe_out = subprocess.run(["tippecanoe"] + tippe_options_list) + except: + WranglerLogger.error() + raise ( + "If tippecanoe isn't installed, try `brew install tippecanoe` or visit https://github.com/mapbox/tippecanoe" + ) + + try: + WranglerLogger.info( + "Running mbview with following options: {}".format( + " ".join(tippe_options_list) + ) + ) + mbview_out = subprocess.run( + ["mbview", "--port", port, ",/{}".format(mbtiles_out)] + ) + except: + WranglerLogger.error() + raise ( + "If mbview isn't installed, try `npm install -g @mapbox/mbview` or visit https://github.com/mapbox/mbview" + ) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/network_wrangler/transitnetwork/index.html b/_modules/network_wrangler/transitnetwork/index.html new file mode 100644 index 00000000..7aef933d --- /dev/null +++ b/_modules/network_wrangler/transitnetwork/index.html @@ -0,0 +1,1601 @@ + + + + + + network_wrangler.transitnetwork — Network Wrangler documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for network_wrangler.transitnetwork

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from __future__ import annotations
+
+import copy
+import os
+import re
+from typing import Tuple, Union
+
+import networkx as nx
+import numpy as np
+import pandas as pd
+import partridge as ptg
+from partridge.config import default_config
+
+from .logger import WranglerLogger
+from .utils import parse_time_spans
+from .roadwaynetwork import RoadwayNetwork
+
+SHAPES_FOREIGN_KEY = "shape_model_node_id"
+STOPS_FOREIGN_KEY = "model_node_id"
+
+ID_SCALAR = 100000000
+
+
+
[docs]class TransitNetwork(object): + """ + Representation of a Transit Network. + + .. highlight:: python + + Typical usage example: + :: + import network_wrangler as wr + stpaul = r'/home/jovyan/work/example/stpaul' + tc=wr.TransitNetwork.read(path=stpaul) + + Attributes: + feed (DotDict): Partridge feed mapping dataframes. + config (nx.DiGraph): Partridge config + road_net (RoadwayNetwork): Associated roadway network object. + graph (nx.MultiDiGraph): Graph for associated roadway network object. + feed_path (str): Where the feed was read in from. + validated_frequencies (bool): The frequencies have been validated. + validated_road_network_consistency (): The network has been validated against the road network. + shapes_foreign_key (str): foreign key between shapes dataframe and roadway network nodes. + stops_foreign_key (str): foreign key between stops dataframe and roadway network nodes. + id_scalar (int): scalar value added to create new stop and shape IDs when necessary. + REQUIRED_FILES (list[str]): list of files that the transit network requires. + """ + + REQUIRED_FILES = [ + "agency.txt", + "frequencies.txt", + "routes.txt", + "shapes.txt", + "stop_times.txt", + "stops.txt", + "trips.txt", + ] + +
[docs] def __init__( + self, + feed: DotDict = None, + config: nx.DiGraph = None, + shapes_foreign_key: str = None, + stops_foreign_key: str = None, + id_scalar: int = None, + ): + """ + Constructor + + .. todo:: Make graph a reference to associated RoadwayNetwork's graph, not its own thing. + """ + self.feed: DotDict = feed + self.config: nx.DiGraph = config + + self.id_scalar = id_scalar + self.shapes_foreign_key = shapes_foreign_key + self.stops_foreign_key = stops_foreign_key + + self.road_net: RoadwayNetwork = None + self.graph: nx.MultiDiGraph = None + self.feed_path = None + + self.validated_frequencies = False + self.validated_road_network_consistency = False + + if not self.validate_frequencies(): + raise ValueError( + "Transit lines with non-positive frequencies exist in the network" + )
+ +
[docs] @staticmethod + def empty() -> TransitNetwork: + """ + Create an empty transit network instance using the default config. + + .. todo:: fill out this method + """ + ##TODO + + msg = "TransitNetwork.empty is not implemented." + WranglerLogger.error(msg) + raise NotImplemented(msg)
+ +
[docs] @staticmethod + def read( + feed_path: str, + shapes_foreign_key: str = SHAPES_FOREIGN_KEY, + stops_foreign_key: str = STOPS_FOREIGN_KEY, + id_scalar: int = ID_SCALAR, + ) -> TransitNetwork: + """ + Read GTFS feed from folder and TransitNetwork object. + + Args: + feed_path: where to read transit network files from. + shapes_foreign_key: foreign key between shapes dataframe and roadway network nodes. Will default to SHAPES_FOREIGN_KEY if not provided. + stops_foreign_key: foreign key between stops dataframe and roadway network nodes. Will defaul to STOPS_FOREIGN_KEY if not provided. + id_scalar: scalar value added to create new stop and shape IDs when necessary. Will default to ID_SCALAR if not provided. + + Returns: a TransitNetwork object. + """ + config = default_config() + feed = ptg.load_feed(feed_path, config=config) + WranglerLogger.info("Read in transit feed from: {}".format(feed_path)) + + updated_config = TransitNetwork.validate_feed(feed, config) + + # Read in each feed so we can write over them + editable_feed = DotDict() + for node in updated_config.nodes.keys(): + # Load (initiate Partridge's lazy load) + editable_feed[node.replace(".txt", "")] = feed.get(node) + + transit_network = TransitNetwork( + feed=editable_feed, + config=updated_config, + shapes_foreign_key=shapes_foreign_key, + stops_foreign_key=stops_foreign_key, + id_scalar=id_scalar, + ) + transit_network.feed_path = feed_path + + fare_attributes_df = pd.read_csv(os.path.join(feed_path, 'fare_attributes.txt')) + fare_rules_df = pd.read_csv(os.path.join(feed_path, 'fare_rules.txt')) + + transit_network.feed.fare_attributes = fare_attributes_df + transit_network.feed.fare_rules = fare_rules_df + + return transit_network
+ +
[docs] @staticmethod + def validate_feed(feed: DotDict, config: nx.DiGraph) -> bool: + """ + Since Partridge lazily loads the df, load each file to make sure it + actually works. + + Partridge uses a DiGraph from the networkx library to represent the + relationships between GTFS files. Each file is a 'node', and the + relationship between files are 'edges'. + + Args: + feed: partridge feed + config: partridge config + """ + updated_config = copy.deepcopy(config) + files_not_found = [] + for node in config.nodes.keys(): + + n = feed.get(node) + WranglerLogger.debug("...{}:\n{}".format(node, n[:10])) + if n.shape[0] == 0: + WranglerLogger.info( + "Removing {} from transit network config because file not found".format( + node + ) + ) + updated_config.remove_node(node) + if node in TransitNetwork.REQUIRED_FILES: + files_not_found.append(node) + + if files_not_found: + msg = "Required files not found or valid: {}".format( + ",".join(files_not_found) + ) + WranglerLogger.error(msg) + raise AttributeError(msg) + return False + + TransitNetwork.validate_network_keys(feed) + + return updated_config
+ +
[docs] def validate_frequencies(self) -> bool: + """ + Validates that there are no transit trips in the feed with zero frequencies. + + Changes state of self.validated_frequencies boolean based on outcome. + + Returns: + boolean indicating if valid or not. + """ + + _valid = True + zero_freq = self.feed.frequencies[self.feed.frequencies.headway_secs <= 0] + + if len(zero_freq.index) > 0: + _valid = False + msg = "Transit lines {} have non-positive frequencies".format( + zero_freq.trip_id.to_list() + ) + WranglerLogger.error(msg) + + self.validated_frequencies = True + + return _valid
+ +
[docs] def validate_road_network_consistencies(self) -> bool: + """ + Validates transit network against the road network for both stops + and shapes. + + Returns: + boolean indicating if valid or not. + """ + if self.road_net is None: + raise ValueError( + "RoadwayNetwork not set yet, see TransitNetwork.set_roadnet()" + ) + + valid = True + + valid_stops = self.validate_transit_stops() + valid_shapes = self.validate_transit_shapes() + + self.validated_road_network_consistency = True + + if not valid_stops or not valid_shapes: + valid = False + raise ValueError("Transit network is not consistent with road network.") + + return valid
+ +
[docs] def validate_transit_stops(self) -> bool: + """ + Validates that all transit stops are part of the roadway network. + + Returns: + Boolean indicating if valid or not. + """ + + if self.road_net is None: + raise ValueError( + "RoadwayNetwork not set yet, see TransitNetwork.set_roadnet()" + ) + + stops = self.feed.stops + nodes = self.road_net.nodes_df + + valid = True + + stop_ids = [int(s) for s in stops[self.stops_foreign_key].to_list()] + node_ids = [int(n) for n in nodes[self.road_net.node_foreign_key].to_list()] + + if not set(stop_ids).issubset(node_ids): + valid = False + missing_stops = list(set(stop_ids) - set(node_ids)) + msg = "Not all transit stops are part of the roadyway network. " + msg += "Missing stops ({}) from the roadway nodes are {}.".format( + self.stops_foreign_key, missing_stops + ) + WranglerLogger.error(msg) + + return valid
+ +
[docs] def validate_transit_shapes(self) -> bool: + """ + Validates that all transit shapes are part of the roadway network. + + Returns: + Boolean indicating if valid or not. + """ + + if self.road_net is None: + raise ValueError( + "RoadwayNetwork not set yet, see TransitNetwork.set_roadnet()" + ) + + shapes_df = self.feed.shapes + nodes_df = self.road_net.nodes_df + links_df = self.road_net.links_df + + valid = True + + # check if all the node ids exist in the network + shape_ids = [int(s) for s in shapes_df[self.shapes_foreign_key].to_list()] + node_ids = [int(n) for n in nodes_df[self.road_net.node_foreign_key].to_list()] + + if not set(shape_ids).issubset(node_ids): + valid = False + missing_shapes = list(set(shape_ids) - set(node_ids)) + msg = "Not all transit shapes are part of the roadyway network. " + msg += "Missing shapes ({}) from the roadway network are {}.".format( + self.shapes_foreign_key, missing_shapes + ) + WranglerLogger.error(msg) + return valid + + # check if all the links in transit shapes exist in the network + # and transit is allowed + shapes_df = shapes_df.astype({self.shapes_foreign_key: int}) + unique_shape_ids = shapes_df.shape_id.unique().tolist() + + for id in unique_shape_ids: + subset_shapes_df = shapes_df[shapes_df["shape_id"] == id] + subset_shapes_df = subset_shapes_df.sort_values(by=["shape_pt_sequence"]) + subset_shapes_df = subset_shapes_df.add_suffix("_1").join( + subset_shapes_df.shift(-1).add_suffix("_2") + ) + subset_shapes_df = subset_shapes_df.dropna() + + merged_df = subset_shapes_df.merge( + links_df, + how="left", + left_on=[ + self.shapes_foreign_key + "_1", + self.shapes_foreign_key + "_2", + ], + right_on=["A", "B"], + indicator=True, + ) + + missing_links_df = merged_df.query('_merge == "left_only"') + + # there are shape links which does not exist in the roadway network + if len(missing_links_df.index) > 0: + valid = False + msg = "There are links for shape id {} which are missing in the roadway network.".format( + id + ) + WranglerLogger.error(msg) + + transit_not_allowed_df = merged_df.query( + '_merge == "both" & drive_access == 0 & bus_only == 0 & rail_only == 0' + ) + + # there are shape links where transit is not allowed + if len(transit_not_allowed_df.index) > 0: + valid = False + msg = "There are links for shape id {} which does not allow transit in the roadway network.".format( + id + ) + WranglerLogger.error(msg) + + return valid
+ +
[docs] @staticmethod + def route_ids_in_routestxt(feed: DotDict) -> Bool: + """ + Wherever route_id occurs, make sure it is in routes.txt + + Args: + feed: partridge feed object + + Returns: + Boolean indicating if feed is okay. + """ + route_ids_routestxt = set(feed.routes.route_id.tolist()) + route_ids_referenced = set(feed.trips.route_id.tolist()) + + missing_routes = route_ids_referenced - route_ids_routestxt + + if missing_routes: + WranglerLogger.warning( + "The following route_ids are referenced but missing from routes.txt: {}".format( + list(missing_routes) + ) + ) + return False + return True
+ +
[docs] @staticmethod + def trip_ids_in_tripstxt(feed: DotDict) -> Bool: + """ + Wherever trip_id occurs, make sure it is in trips.txt + + Args: + feed: partridge feed object + + Returns: + Boolean indicating if feed is okay. + """ + trip_ids_tripstxt = set(feed.trips.trip_id.tolist()) + trip_ids_referenced = set( + feed.stop_times.trip_id.tolist() + feed.frequencies.trip_id.tolist() + ) + + missing_trips = trip_ids_referenced - trip_ids_tripstxt + + if missing_trips: + WranglerLogger.warning( + "The following trip_ids are referenced but missing from trips.txt: {}".format( + list(missing_trips) + ) + ) + return False + return True
+ +
[docs] @staticmethod + def shape_ids_in_shapestxt(feed: DotDict) -> Bool: + """ + Wherever shape_id occurs, make sure it is in shapes.txt + + Args: + feed: partridge feed object + + Returns: + Boolean indicating if feed is okay. + """ + + shape_ids_shapestxt = set(feed.shapes.shape_id.tolist()) + shape_ids_referenced = set(feed.trips.shape_id.tolist()) + + missing_shapes = shape_ids_referenced - shape_ids_shapestxt + + if missing_shapes: + WranglerLogger.warning( + "The following shape_ids from trips.txt are missing from shapes.txt: {}".format( + list(missing_shapes) + ) + ) + return False + return True
+ +
[docs] @staticmethod + def stop_ids_in_stopstxt(feed: DotDict) -> Bool: + """ + Wherever stop_id occurs, make sure it is in stops.txt + + Args: + feed: partridge feed object + + Returns: + Boolean indicating if feed is okay. + """ + stop_ids_stopstxt = set(feed.stops.stop_id.tolist()) + stop_ids_referenced = [] + + # STOP_TIMES + stop_ids_referenced.extend(feed.stop_times.stop_id.dropna().tolist()) + stop_ids_referenced.extend(feed.stops.parent_station.dropna().tolist()) + + # TRANSFERS + if feed.get("transfers.txt").shape[0] > 0: + stop_ids_referenced.extend(feed.transfers.from_stop_id.dropna().tolist()) + stop_ids_referenced.extend(feed.transfers.to_stop_id.dropna().tolist()) + + # PATHWAYS + if feed.get("pathways.txt").shape[0] > 0: + stop_ids_referenced.extend(feed.pathways.from_stop_id.dropna().tolist()) + stop_ids_referenced.extend(feed.pathways.to_stop_id.dropna().tolist()) + + stop_ids_referenced = set(stop_ids_referenced) + + missing_stops = stop_ids_referenced - stop_ids_stopstxt + + if missing_stops: + WranglerLogger.warning( + "The following stop_ids from are referenced but missing from stops.txt: {}".format( + list(missing_stops) + ) + ) + return False + return True
+ +
[docs] @staticmethod + def validate_network_keys(feed: DotDict) -> Bool: + """ + Validates foreign keys are present in all connecting feed files. + + Args: + feed: partridge feed object + + Returns: + Boolean indicating if feed is okay. + """ + result = True + result = result and TransitNetwork.route_ids_in_routestxt(feed) + result = result and TransitNetwork.trip_ids_in_tripstxt(feed) + result = result and TransitNetwork.shape_ids_in_shapestxt(feed) + result = result and TransitNetwork.stop_ids_in_stopstxt(feed) + return result
+ +
[docs] def set_roadnet( + self, + road_net: RoadwayNetwork, + graph_shapes: bool = False, + graph_stops: bool = False, + validate_consistency: bool = True, + ) -> None: + self.road_net: RoadwayNetwork = road_net + self.graph: nx.MultiDiGraph = RoadwayNetwork.ox_graph( + road_net.nodes_df, road_net.links_df + ) + if graph_shapes: + self._graph_shapes() + if graph_stops: + self._graph_stops() + + if validate_consistency: + self.validate_road_network_consistencies()
+ + def _graph_shapes(self) -> None: + """ + + .. todo:: Fill out this method. + """ + existing_shapes = self.feed.shapes + msg = "_graph_shapes() not implemented yet." + WranglerLogger.error(msg) + raise NotImplemented(msg) + # graphed_shapes = pd.DataFrame() + + # for shape_id in shapes: + # TODO traverse point by point, mapping shortest path on graph, + # then append to a list + # return total list of all link ids + # rebuild rows in shapes dataframe and add to graphed_shapes + # make graphed_shapes a GeoDataFrame + + # self.feed.shapes = graphed_shapes + + def _graph_stops(self) -> None: + """ + .. todo:: Fill out this method. + """ + existing_stops = self.feed.stops + msg = "_graph_stops() not implemented yet." + WranglerLogger.error(msg) + raise NotImplemented(msg) + # graphed_stops = pd.DataFrame() + + # for stop_id in stops: + # TODO + + # self.feed.stops = graphed_stops + +
[docs] def write(self, path: str = ".", filename: str = None) -> None: + """ + Writes a network in the transit network standard + + Args: + path: the path were the output will be saved + filename: the name prefix of the transit files that will be generated + """ + WranglerLogger.info("Writing transit to directory: {}".format(path)) + for node in self.config.nodes.keys(): + + df = self.feed.get(node.replace(".txt", "")) + if not df.empty: + if filename: + outpath = os.path.join(path, filename + "_" + node) + else: + outpath = os.path.join(path, node) + WranglerLogger.debug("Writing file: {}".format(outpath)) + + df.to_csv(outpath, index=False)
+ +
[docs] @staticmethod + def transit_net_to_gdf(transit: Union(TransitNetwork, pd.DataFrame)): + """ + Returns a geodataframe given a TransitNetwork or a valid Shapes DataFrame. + + Args: + transit: either a TransitNetwork or a Shapes GeoDataFrame + + .. todo:: Make more sophisticated. + """ + from partridge import geo + + if type(transit) is pd.DataFrame: + shapes = transit + else: + shapes = transit.feed.shapes + + transit_gdf = geo.build_shapes(shapes) + return transit_gdf
+ +
[docs] def apply(self, project_card_dictionary: dict): + """ + Wrapper method to apply a project to a transit network. + + Args: + project_card_dictionary: dict + a dictionary of the project card object + + """ + WranglerLogger.info( + "Applying Project to Transit Network: {}".format( + project_card_dictionary["project"] + ) + ) + + def _apply_individual_change(project_dictionary: dict): + if ( + project_dictionary["category"].lower() + == "transit service property change" + ): + self.apply_transit_feature_change( + self.select_transit_features(project_dictionary["facility"]), + project_dictionary["properties"], + ) + elif project_dictionary["category"].lower() == "parallel managed lanes": + # Grab the list of nodes in the facility from road_net + # It should be cached because managed lane projects are + # processed by RoadwayNetwork first via + # Scenario.apply_all_projects + try: + managed_lane_nodes = self.road_net.selections( + self.road_net.build_selection_key( + project_dictionary["facility"] + ) + )["route"] + except ValueError: + WranglerLogger.error( + "RoadwayNetwork not set yet, see TransitNetwork.set_roadnet()" + ) + + # Reroute any transit using these nodes + self.apply_transit_managed_lane( + self.select_transit_features_by_nodes(managed_lane_nodes), + managed_lane_nodes, + self.RoadNet.managed_lanes_node_id_scalar, + ) + elif project_dictionary["category"].lower() == "add transit": + self.apply_python_calculation(project_dictionary["pycode"]) + elif project_dictionary["category"].lower() == "roadway deletion": + WranglerLogger.warning( + "Roadway Deletion not yet implemented in Transit; ignoring" + ) + else: + msg = "{} not implemented yet in TransitNetwork; can't apply.".format( + project_dictionary["category"] + ) + WranglerLogger.error(msg) + raise (msg) + + if project_card_dictionary.get("changes"): + for project_dictionary in project_card_dictionary["changes"]: + _apply_individual_change(project_dictionary) + else: + _apply_individual_change(project_card_dictionary)
+ +
[docs] def apply_python_calculation( + self, pycode: str, in_place: bool = True + ) -> Union(None, TransitNetwork): + """ + Changes roadway network object by executing pycode. + + Args: + pycode: python code which changes values in the roadway network object + in_place: update self or return a new roadway network object + """ + exec(pycode)
+ +
[docs] def select_transit_features(self, selection: dict) -> pd.Series: + """ + combines multiple selections + + Args: + selection : selection dictionary + + Returns: trip identifiers : list of GTFS trip IDs in the selection + """ + trip_ids = pd.Series() + + if selection.get("route"): + for route_dictionary in selection["route"]: + trip_ids = trip_ids.append( + self._select_transit_features(route_dictionary) + ) + else: + trip_ids = self._select_transit_features(selection) + + return trip_ids
+ + def _select_transit_features(self, selection: dict) -> pd.Series: + """ + Selects transit features that satisfy selection criteria + + Args: + selection : selection dictionary + + Returns: trip identifiers : list of GTFS trip IDs in the selection + """ + trips = self.feed.trips + routes = self.feed.routes + freq = self.feed.frequencies + + # Turn selection's values into lists if they are not already + for key in selection.keys(): + if type(selection[key]) not in [list, tuple]: + selection[key] = [selection[key]] + + # Based on the key in selection, filter trips + if "trip_id" in selection: + trips = trips[trips.trip_id.isin(selection["trip_id"])] + + elif "route_id" in selection: + trips = trips[trips.route_id.isin(selection["route_id"])] + + elif "route_short_name" in selection: + routes = routes[routes.route_short_name.isin(selection["route_short_name"])] + trips = trips[trips.route_id.isin(routes["route_id"])] + + elif "route_long_name" in selection: + matches = [] + for sel in selection["route_long_name"]: + for route_long_name in routes["route_long_name"]: + x = re.search(sel, route_long_name) + if x is not None: + matches.append(route_long_name) + + routes = routes[routes.route_long_name.isin(matches)] + trips = trips[trips.route_id.isin(routes["route_id"])] + + else: + WranglerLogger.error("Selection not supported %s", selection.keys()) + raise ValueError + + # If a time key exists, filter trips using frequency table + if selection.get("time"): + selection["time"] = parse_time_spans(selection["time"]) + elif selection.get("start_time") and selection.get("end_time"): + selection["time"] = parse_time_spans( + [selection["start_time"][0], selection["end_time"][0]] + ) + # Filter freq to trips in selection + freq = freq[freq.trip_id.isin(trips["trip_id"])] + freq = freq[freq.start_time == selection["time"][0]] + freq = freq[freq.end_time == selection["time"][1]] + + # Filter trips table to those still in freq table + trips = trips[trips.trip_id.isin(freq["trip_id"])] + + # If any other key exists, filter routes or trips accordingly + for key in selection.keys(): + if key not in [ + "trip_id", + "route_id", + "route_short_name", + "route_long_name", + "time", + "start_time", + "end_time" + ]: + if key in trips: + trips = trips[trips[key].isin(selection[key])] + elif key in routes: + routes = routes[routes[key].isin(selection[key])] + trips = trips[trips.route_id.isin(routes["route_id"])] + else: + WranglerLogger.error("Selection not supported %s", key) + raise ValueError + + # Check that there is at least one trip in trips table or raise error + if len(trips) < 1: + WranglerLogger.error("Selection returned zero trips") + raise ValueError + + # Return pandas.Series of trip_ids + return trips["trip_id"] + +
[docs] def select_transit_features_by_nodes( + self, node_ids: list, require_all: bool = False + ) -> pd.Series: + """ + Selects transit features that use any one of a list of node_ids + + Args: + node_ids: list (generally coming from nx.shortest_path) + require_all : bool if True, the returned trip_ids must traverse all of + the nodes (default = False) + + Returns: + trip identifiers list of GTFS trip IDs in the selection + """ + # If require_all, the returned trip_ids must traverse all of the nodes + # Else, filter any shapes that use any one of the nodes in node_ids + if require_all: + shape_ids = ( + self.feed.shapes.groupby("shape_id").filter( + lambda x: all( + i in x[self.shapes_foreign_key].tolist() for i in node_ids + ) + ) + ).shape_id.drop_duplicates() + else: + shape_ids = self.feed.shapes[ + self.feed.shapes[self.shapes_foreign_key].isin(node_ids) + ].shape_id.drop_duplicates() + + # Return pandas.Series of trip_ids + return self.feed.trips[self.feed.trips.shape_id.isin(shape_ids)].trip_id
+ +
[docs] def check_network_connectivity(self, shapes_foreign_key : pd.Series) -> pd.Series: + """ + check if new shapes contain any links that are not in the roadway network + """ + shape_links_df = pd.DataFrame( + { + "A" : shapes_foreign_key.tolist()[:-1], + "B" : shapes_foreign_key.tolist()[1:], + } + ) + + shape_links_df["A"] = shape_links_df["A"].astype(int) + shape_links_df["B"] = shape_links_df["B"].astype(int) + + shape_links_df = pd.merge( + shape_links_df, + self.road_net.links_df[["A", "B", "model_link_id"]], + how = "left", + on = ["A", "B"] + ) + + missing_shape_links_df = shape_links_df[shape_links_df["model_link_id"].isnull()] + + if len(missing_shape_links_df) > 0: + for index, row in missing_shape_links_df.iterrows(): + WranglerLogger.warning( + "Missing connections from node {} to node {} for the new routing, find complete path using default graph".format(int(row.A), int(row.B)) + ) + + complete_node_list = TransitNetwork.route_between_nodes(self.graph, row.A, row.B) + complete_node_list = pd.Series([str(int(i)) for i in complete_node_list]) + + WranglerLogger.info( + "Routing path from node {} to node {} for missing connections: {}.".format(int(row.A), int(row.B), complete_node_list.tolist()) + ) + + nodes = shapes_foreign_key.tolist() + index_replacement_starts = [i for i,d in enumerate(nodes) if d == str(int(row.A))][0] + index_replacement_ends = [i for i,d in enumerate(nodes) if d == str(int(row.B))][-1] + shapes_foreign_key = pd.concat( + [ + shapes_foreign_key.iloc[:index_replacement_starts], + complete_node_list, + shapes_foreign_key.iloc[index_replacement_ends + 1 :], + ], + ignore_index=True, + sort=False, + ) + + return shapes_foreign_key
+ +
[docs] @staticmethod + def route_between_nodes(graph, A, B) -> list: + """ + find complete path when the new shape has connectivity issue + """ + + node_list = nx.shortest_path( + graph, + A, + B, + weight = "length" + ) + + return node_list
+ +
[docs] def apply_transit_feature_change( + self, trip_ids: pd.Series, properties: list, in_place: bool = True + ) -> Union(None, TransitNetwork): + """ + Changes the transit attributes for the selected features based on the + project card information passed + + Args: + trip_ids : pd.Series + all trip_ids to apply change to + properties : list of dictionaries + transit properties to change + in_place : bool + whether to apply changes in place or return a new network + + Returns: + None + """ + for i in properties: + if i["property"] in ["headway_secs"]: + self._apply_transit_feature_change_frequencies(trip_ids, i, in_place) + + elif i["property"] in ["routing"]: + self._apply_transit_feature_change_routing(trip_ids, i, in_place) + + elif i["property"] in ["shapes"]: + self._apply_transit_feature_change_shapes(trip_ids, i, in_place) + + elif i["property"] in ['no_alightings', 'no_boardings', 'allow_alightings', 'allow_boardings']: + self._apply_transit_feature_change_stops(trip_ids, i, in_place)
+ + def _apply_transit_feature_change_routing( + self, trip_ids: pd.Series, properties: dict, in_place: bool = True + ) -> Union(None, TransitNetwork): + shapes = self.feed.shapes.copy() + stop_times = self.feed.stop_times.copy() + stops = self.feed.stops.copy() + + # A negative sign in "set" indicates a traversed node without a stop + # If any positive numbers, stops have changed + stops_change = False + if any(x > 0 for x in properties["set"]): + # Simplify "set" and "existing" to only stops + properties["set_stops"] = [str(i) for i in properties["set"] if i > 0] + if properties.get("existing") is not None: + properties["existing_stops"] = [ + str(i) for i in properties["existing"] if i > 0 + ] + stops_change = True + + # Convert ints to objects + properties["set_shapes"] = [str(abs(i)) for i in properties["set"]] + if properties.get("existing") is not None: + properties["existing_shapes"] = [ + str(abs(i)) for i in properties["existing"] + ] + + # Replace shapes records + trips = self.feed.trips # create pointer rather than a copy + shape_ids = trips[trips["trip_id"].isin(trip_ids)].shape_id + for shape_id in set(shape_ids): + # Check if `shape_id` is used by trips that are not in + # parameter `trip_ids` + trips_using_shape_id = trips.loc[trips["shape_id"] == shape_id, ["trip_id"]] + if not all(trips_using_shape_id.isin(trip_ids)["trip_id"]): + # In this case, we need to create a new shape_id so as to leave + # the trips not part of the query alone + WranglerLogger.warning( + "Trips that were not in your query selection use the " + "same `shape_id` as trips that are in your query. Only " + "the trips' shape in your query will be changed." + ) + old_shape_id = shape_id + shape_id = str(int(shape_id) + self.id_scalar) + if shape_id in shapes["shape_id"].tolist(): + WranglerLogger.error("Cannot create a unique new shape_id.") + dup_shape = shapes[shapes.shape_id == old_shape_id].copy() + dup_shape["shape_id"] = shape_id + shapes = pd.concat([shapes, dup_shape], ignore_index=True) + + # Pop the rows that match shape_id + this_shape = shapes[shapes.shape_id == shape_id] + + # Make sure they are ordered by shape_pt_sequence + this_shape = this_shape.sort_values(by=["shape_pt_sequence"]) + + # Build a pd.DataFrame of new shape records + new_shape_rows = pd.DataFrame( + { + "shape_id": shape_id, + "shape_pt_lat": None, # FIXME Populate from self.road_net? + "shape_pt_lon": None, # FIXME + "shape_osm_node_id": None, # FIXME + "shape_pt_sequence": None, + self.shapes_foreign_key: properties["set_shapes"], + } + ) + + check_new_shape_nodes = self.check_network_connectivity(new_shape_rows[self.shapes_foreign_key]) + + if len(check_new_shape_nodes) != len(new_shape_rows): + new_shape_rows = pd.DataFrame( + { + "shape_id": shape_id, + "shape_pt_lat": None, # FIXME Populate from self.road_net? + "shape_pt_lon": None, # FIXME + "shape_osm_node_id": None, # FIXME + "shape_pt_sequence": None, + self.shapes_foreign_key: check_new_shape_nodes, + } + ) + properties["set_shapes"] = check_new_shape_nodes.tolist() + + # If "existing" is specified, replace only that segment + # Else, replace the whole thing + if properties.get("existing") is not None: + # Match list + nodes = this_shape[self.shapes_foreign_key].tolist() + index_replacement_starts = [i for i,d in enumerate(nodes) if d == properties["existing_shapes"][0]][0] + index_replacement_ends = [i for i,d in enumerate(nodes) if d == properties["existing_shapes"][-1]][-1] + this_shape = pd.concat( + [ + this_shape.iloc[:index_replacement_starts], + new_shape_rows, + this_shape.iloc[index_replacement_ends + 1 :], + ], + ignore_index=True, + sort=False, + ) + else: + this_shape = new_shape_rows + + # Renumber shape_pt_sequence + this_shape["shape_pt_sequence"] = np.arange(len(this_shape)) + + # Add rows back into shapes + shapes = pd.concat( + [shapes[shapes.shape_id != shape_id], this_shape], + ignore_index=True, + sort=False, + ) + + # Replace stop_times and stops records (if required) + if stops_change: + # If node IDs in properties["set_stops"] are not already + # in stops.txt, create a new stop_id for them in stops + existing_fk_ids = set(stops[self.stops_foreign_key].tolist()) + nodes_df = self.road_net.nodes_df.loc[:, [self.stops_foreign_key, "X", "Y"]] + for fk_i in properties["set_stops"]: + if fk_i not in existing_fk_ids: + WranglerLogger.info( + "Creating a new stop in stops.txt for node ID: {}".format(fk_i) + ) + # Add new row to stops + new_stop_id = str(int(fk_i) + self.id_scalar) + if new_stop_id in stops["stop_id"].tolist(): + WranglerLogger.error("Cannot create a unique new stop_id.") + stops.loc[ + len(stops.index) + 1, + ["stop_id", "stop_lat", "stop_lon", self.stops_foreign_key,], + ] = [ + new_stop_id, + nodes_df.loc[nodes_df[self.stops_foreign_key] == int(fk_i), "Y"], + nodes_df.loc[nodes_df[self.stops_foreign_key] == int(fk_i), "X"], + fk_i, + ] + + # Loop through all the trip_ids + for trip_id in trip_ids: + # Pop the rows that match trip_id + this_stoptime = stop_times[stop_times.trip_id == trip_id] + + # Merge on node IDs using stop_id (one node ID per stop_id) + this_stoptime = this_stoptime.merge( + stops[["stop_id", self.stops_foreign_key]], + how="left", + on="stop_id", + ) + + # Make sure the stop_times are ordered by stop_sequence + this_stoptime = this_stoptime.sort_values(by=["stop_sequence"]) + + # Build a pd.DataFrame of new shape records from properties + new_stoptime_rows = pd.DataFrame( + { + "trip_id": trip_id, + "arrival_time": None, + "departure_time": None, + "pickup_type": None, + "drop_off_type": None, + "stop_distance": None, + "timepoint": None, + "stop_is_skipped": None, + self.stops_foreign_key: properties["set_stops"], + } + ) + + # Merge on stop_id using node IDs (many stop_id per node ID) + new_stoptime_rows = ( + new_stoptime_rows.merge( + stops[["stop_id", self.stops_foreign_key]], + how="left", + on=self.stops_foreign_key, + ) + .groupby([self.stops_foreign_key]) + .head(1) + ) # pick first + + # If "existing" is specified, replace only that segment + # Else, replace the whole thing + if properties.get("existing") is not None: + # Match list (remember stops are passed in with node IDs) + nodes = this_stoptime[self.stops_foreign_key].tolist() + index_replacement_starts = nodes.index( + properties["existing_stops"][0] + ) + index_replacement_ends = nodes.index( + properties["existing_stops"][-1] + ) + this_stoptime = pd.concat( + [ + this_stoptime.iloc[:index_replacement_starts], + new_stoptime_rows, + this_stoptime.iloc[index_replacement_ends + 1 :], + ], + ignore_index=True, + sort=False, + ) + else: + this_stoptime = new_stoptime_rows + + # Remove node ID + del this_stoptime[self.stops_foreign_key] + + # Renumber stop_sequence + this_stoptime["stop_sequence"] = np.arange(len(this_stoptime)) + + # Add rows back into stoptime + stop_times = pd.concat( + [stop_times[stop_times.trip_id != trip_id], this_stoptime], + ignore_index=True, + sort=False, + ) + + # Replace self if in_place, else return + if in_place: + self.feed.shapes = shapes + self.feed.stops = stops + self.feed.stop_times = stop_times + else: + updated_network = copy.deepcopy(self) + updated_network.feed.shapes = shapes + updated_network.feed.stops = stops + updated_network.feed.stop_times = stop_times + return updated_network + + def _apply_transit_feature_change_frequencies( + self, trip_ids: pd.Series, properties: dict, in_place: bool = True + ) -> Union(None, TransitNetwork): + freq = self.feed.frequencies.copy() + + # Grab only those records matching trip_ids (aka selection) + freq = freq[freq.trip_id.isin(trip_ids)] + + # Check all `existing` properties if given + if properties.get("existing") is not None: + if not all(freq.headway_secs == properties["existing"]): + WranglerLogger.error( + "Existing does not match for at least " + "1 trip in:\n {}".format(trip_ids.to_string()) + ) + raise ValueError + + # Calculate build value + if properties.get("set") is not None: + build_value = properties["set"] + else: + build_value = [i + properties["change"] for i in freq.headway_secs] + + # Update self or return a new object + q = self.feed.frequencies.trip_id.isin(freq["trip_id"]) + if in_place: + self.feed.frequencies.loc[q, properties["property"]] = build_value + else: + updated_network = copy.deepcopy(self) + updated_network.loc[q, properties["property"]] = build_value + return updated_network + +
[docs] def apply_transit_managed_lane( + self, trip_ids: pd.Series, node_ids: list, scalar: int, in_place: bool = True + ) -> Union(None, TransitNetwork): + # Traversed nodes without a stop should be negative integers + all_stops = self.feed.stops[self.stops_foreign_key].tolist() + node_ids = [int(x) if str(x) in all_stops else int(x) * -1 for x in node_ids] + + self._apply_transit_feature_change_routing( + trip_ids=trip_ids, + properties={ + "existing": node_ids, + "set": RoadwayNetwork.get_managed_lane_node_ids(node_ids, scalar), + }, + in_place=in_place, + )
+ + def _apply_transit_feature_change_shapes( + self, trip_ids: pd.Series, properties: dict, in_place: bool = True + ) -> Union(None, TransitNetwork): + shapes = self.feed.shapes.copy() + stop_times = self.feed.stop_times.copy() + stops = self.feed.stops.copy() + + properties["set_shapes"] = [str(abs(i)) for i in properties["set"]] + if properties.get("existing") is not None: + properties["existing_shapes"] = [ + str(abs(i)) for i in properties["existing"] + ] + + # Replace shapes records + trips = self.feed.trips # create pointer rather than a copy + shape_ids = trips[trips["trip_id"].isin(trip_ids)].shape_id + + for shape_id in set(shape_ids): + # Check if `shape_id` is used by trips that are not in + # parameter `trip_ids` + trips_using_shape_id = trips.loc[trips["shape_id"] == shape_id, ["trip_id"]] + if not all(trips_using_shape_id.isin(trip_ids)["trip_id"]): + # In this case, we need to create a new shape_id so as to leave + # the trips not part of the query alone + WranglerLogger.warning( + "Trips that were not in your query selection use the " + "same `shape_id` as trips that are in your query. Only " + "the trips' shape in your query will be changed." + ) + old_shape_id = shape_id + shape_id = str(int(shape_id) + self.id_scalar) + if shape_id in shapes["shape_id"].tolist(): + WranglerLogger.error("Cannot create a unique new shape_id.") + dup_shape = shapes[shapes.shape_id == old_shape_id].copy() + dup_shape["shape_id"] = shape_id + shapes = pd.concat([shapes, dup_shape], ignore_index=True) + # change the shape_id for the trip record + trips.loc[trips["trip_id"].isin(trip_ids), "shape_id"] = shape_id + + # Pop the rows that match shape_id + this_shape = shapes[shapes.shape_id == shape_id] + + # Make sure they are ordered by shape_pt_sequence + this_shape = this_shape.sort_values(by=["shape_pt_sequence"]) + + # Build a pd.DataFrame of new shape records + new_shape_rows = pd.DataFrame( + { + "shape_id": shape_id, + "shape_pt_lat": None, # FIXME Populate from self.road_net? + "shape_pt_lon": None, # FIXME + "shape_osm_node_id": None, # FIXME + "shape_pt_sequence": None, + self.shapes_foreign_key: properties["set_shapes"], + } + ) + + check_new_shape_nodes = self.check_network_connectivity(new_shape_rows[self.shapes_foreign_key]) + + if len(check_new_shape_nodes) != len(new_shape_rows): + new_shape_rows = pd.DataFrame( + { + "shape_id": shape_id, + "shape_pt_lat": None, # FIXME Populate from self.road_net? + "shape_pt_lon": None, # FIXME + "shape_osm_node_id": None, # FIXME + "shape_pt_sequence": None, + self.shapes_foreign_key: check_new_shape_nodes, + } + ) + properties["set_shapes"] = check_new_shape_nodes.tolist() + + # If "existing" is specified, replace only that segment + # Else, replace the whole thing + if properties.get("existing") is not None: + # Match list + nodes = this_shape[self.shapes_foreign_key].tolist() + index_replacement_starts = [i for i,d in enumerate(nodes) if d == properties["existing_shapes"][0]][0] + index_replacement_ends = [i for i,d in enumerate(nodes) if d == properties["existing_shapes"][-1]][-1] + this_shape = pd.concat( + [ + this_shape.iloc[:index_replacement_starts], + new_shape_rows, + this_shape.iloc[index_replacement_ends + 1 :], + ], + ignore_index=True, + sort=False, + ) + else: + this_shape = new_shape_rows + + # Renumber shape_pt_sequence + this_shape["shape_pt_sequence"] = np.arange(len(this_shape)) + + # Add rows back into shapes + shapes = pd.concat( + [shapes[shapes.shape_id != shape_id], this_shape], + ignore_index=True, + sort=False, + ) + + # Replace self if in_place, else return + if in_place: + self.feed.shapes = shapes + else: + updated_network = copy.deepcopy(self) + updated_network.feed.shapes = shapes + return updated_network + + def _apply_transit_feature_change_stops( + self, trip_ids: pd.Series, properties: dict, in_place: bool = True + ) -> Union(None, TransitNetwork): + shapes = self.feed.shapes.copy() + stop_times = self.feed.stop_times.copy() + stops = self.feed.stops.copy() + trips = self.feed.trips.copy() + nodes_df = self.road_net.nodes_df.loc[:, [self.stops_foreign_key, "X", "Y"]] + + for node_id in properties['set']: + + # check if new stop node + existing_stop_fk_ids = set(stops[self.stops_foreign_key].tolist()) + if str(node_id) not in existing_stop_fk_ids: + WranglerLogger.info( + "Creating a new stop in stops.txt for node ID: {}".format(node_id) + ) + # Add new row to stops + new_stop_id = str(int(node_id) + self.id_scalar) + if new_stop_id in stops["stop_id"].tolist(): + WranglerLogger.error("Cannot create a unique new stop_id.") + stops.loc[ + len(stops.index) + 1, + ["stop_id", "stop_lat", "stop_lon", self.stops_foreign_key,], + ] = [ + new_stop_id, + nodes_df.loc[nodes_df[self.stops_foreign_key] == int(node_id), "Y"], + nodes_df.loc[nodes_df[self.stops_foreign_key] == int(node_id), "X"], + str(node_id), + ] + else: + WranglerLogger.info( + "Modifying existing stop in stop_times.txt for node ID: {}".format(node_id) + ) + for trip_id in trip_ids: + # Pop the rows that match trip_id + this_stoptime = stop_times[stop_times.trip_id == trip_id].copy() + + # Merge on node IDs using stop_id (one node ID per stop_id) + this_stoptime = this_stoptime.merge( + stops[["stop_id", self.stops_foreign_key]], + how="left", + on="stop_id", + ) + + stop_id = this_stoptime[this_stoptime[self.stops_foreign_key] == str(node_id)]['stop_id'].iloc[0] + + if properties['property'] == 'allow_alightings': + stop_times.loc[ + (stop_times['trip_id'] == trip_id) & (stop_times['stop_id'] == stop_id), + 'pickup_type' + ] = 0 + if properties['property'] == 'no_alightings': + stop_times.loc[ + (stop_times['trip_id'] == trip_id) & (stop_times['stop_id'] == stop_id), + 'pickup_type' + ] = 1 + if properties['property'] == 'allow_boardings': + stop_times.loc[ + (stop_times['trip_id'] == trip_id) & (stop_times['stop_id'] == stop_id), + 'drop_off_type' + ] = 0 + if properties['property'] == 'no_boardings': + stop_times.loc[ + (stop_times['trip_id'] == trip_id) & (stop_times['stop_id'] == stop_id), + 'drop_off_type' + ] = 1 + + continue + + for trip_id in trip_ids: + # Pop the rows that match trip_id + this_stoptime = stop_times[stop_times.trip_id == trip_id].copy() + + # Merge on node IDs using stop_id (one node ID per stop_id) + this_stoptime = this_stoptime.merge( + stops[["stop_id", self.stops_foreign_key]], + how="left", + on="stop_id", + ) + + # Make sure the stop_times are ordered by stop_sequence + this_stoptime = this_stoptime.sort_values(by=["stop_sequence"]) + + # get shapes + shape_id = trips[trips.trip_id == trip_id].shape_id.iloc[0] + + this_shape = shapes[shapes.shape_id == shape_id].copy() + + # Make sure the shapes are ordered by shape_sequence + this_shape = this_shape.sort_values(by=["shape_pt_sequence"]) + + this_shape['is_stop'] = np.where( + (this_shape[self.shapes_foreign_key].isin(this_stoptime[self.stops_foreign_key])) | + (this_shape[self.shapes_foreign_key] == str(node_id)), + 1, + 0 + ) + + stops_on_this_shape = this_shape[this_shape.is_stop == 1].copy() + stops_node_list = stops_on_this_shape[self.shapes_foreign_key].tolist() + + this_stop_index = stops_node_list.index(str(node_id)) + + # the stop node id before this stop + previous_stop_node_id = stops_node_list[this_stop_index - 1] + + # the stop node id after this stop + next_stop_node_id = stops_node_list[this_stop_index + 1] + + stoptime_node_ids = this_stoptime[self.stops_foreign_key].tolist() + + index_replacement_starts = stoptime_node_ids.index( + previous_stop_node_id + ) + + index_replacement_ends = stoptime_node_ids.index( + next_stop_node_id + ) + + pickup_type = 0 + drop_off_type = 0 + if properties['property'] == 'allow_alightings': + pickup_type = 0 + if properties['property'] == 'no_alightings': + pickup_type = 1 + if properties['property'] == 'allow_boardings': + drop_off_type = 0 + if properties['property'] == 'no_boardings': + drop_off_type = 1 + + # Build a pd.DataFrame of new shape records from properties + new_stoptime_rows = pd.DataFrame( + { + "trip_id": trip_id, + "arrival_time": None, + "departure_time": None, + "pickup_type": pickup_type, + "drop_off_type": drop_off_type, + "stop_distance": None, + "timepoint": None, + "stop_is_skipped": None, + self.stops_foreign_key: [str(node_id)], + } + ) + + # Merge on stop_id using node IDs (many stop_id per node ID) + new_stoptime_rows = ( + new_stoptime_rows.merge( + stops[["stop_id", self.stops_foreign_key]], + how="left", + on=self.stops_foreign_key, + ) + .groupby([self.stops_foreign_key]) + .head(1) + ) # pick first + + this_stoptime = pd.concat( + [ + this_stoptime.iloc[:index_replacement_starts + 1], + new_stoptime_rows, + this_stoptime.iloc[index_replacement_ends :], + ], + ignore_index=True, + sort=False, + ) + + # Remove node ID + del this_stoptime[self.stops_foreign_key] + + # Renumber stop_sequence + this_stoptime["stop_sequence"] = np.arange(len(this_stoptime)) + + # Add rows back into stoptime + stop_times = pd.concat( + [stop_times[stop_times.trip_id != trip_id], this_stoptime], + ignore_index=True, + sort=False, + ) + + # Replace self if in_place, else return + if in_place: + self.feed.shapes = shapes + self.feed.stops = stops + self.feed.stop_times = stop_times + else: + updated_network = copy.deepcopy(self) + updated_network.feed.shapes = shapes + updated_network.feed.stops = stops + updated_network.feed.stop_times = stop_times + return updated_network
+ +class DotDict(dict): + """ + dot.notation access to dictionary attributes + Source: https://stackoverflow.com/questions/2352181/how-to-use-a-dot-to-access-members-of-dictionary + """ + + __getattr__ = dict.__getitem__ + __setattr__ = dict.__setitem__ + __delattr__ = dict.__delitem__ + + def __getattr__(self, key): + try: + return self[key] + except KeyError: + raise AttributeError(key) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/network_wrangler/utils/index.html b/_modules/network_wrangler/utils/index.html new file mode 100644 index 00000000..45637b08 --- /dev/null +++ b/_modules/network_wrangler/utils/index.html @@ -0,0 +1,520 @@ + + + + + + network_wrangler.utils — Network Wrangler documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for network_wrangler.utils

+import os
+import copy
+import hashlib
+import math
+from typing import Union, Collection
+
+import numpy as np
+import pandas as pd
+import geopandas as gpd
+
+from shapely.geometry import LineString
+from geographiclib.geodesic import Geodesic
+
+from .logger import WranglerLogger
+
+
+
[docs]def point_df_to_geojson( + df: pd.DataFrame, + properties: list, + node_foreign_key = None): + """ + Author: Geoff Boeing: + https://geoffboeing.com/2015/10/exporting-python-data-geojson/ + """ + from .roadwaynetwork import NODE_FOREIGN_KEY + if not node_foreign_key: node_foreign_key = NODE_FOREIGN_KEY + + geojson = {"type": "FeatureCollection", "features": []} + for _, row in df.iterrows(): + feature = { + "type": "Feature", + "properties": {}, + "geometry": {"type": "Point", "coordinates": []}, + } + feature["geometry"]["coordinates"] = [row["geometry"].x, row["geometry"].y] + feature["properties"][node_foreign_key] = row.name + for prop in properties: + feature["properties"][prop] = row[prop] + geojson["features"].append(feature) + return geojson
+ + + + + +
[docs]def topological_sort(adjacency_list, visited_list): + """ + Topological sorting for Acyclic Directed Graph + """ + + output_stack = [] + + def _topology_sort_util(vertex): + if not visited_list[vertex]: + visited_list[vertex] = True + for neighbor in adjacency_list[vertex]: + _topology_sort_util(neighbor) + output_stack.insert(0, vertex) + + for vertex in visited_list: + _topology_sort_util(vertex) + + return output_stack
+ + +
[docs]def make_slug(text, delimiter: str = "_"): + """ + makes a slug from text + """ + import re + + text = re.sub("[,.;@#?!&$']+", "", text.lower()) + return re.sub("[\ ]+", delimiter, text)
+ + +
[docs]def parse_time_spans(times): + """ + parse time spans into tuples of seconds from midnight + can also be used as an apply function for a pandas series + Parameters + ----------- + times: tuple(string) or tuple(int) or list(string) or list(int) + + returns + -------- + tuple(integer) + time span as seconds from midnight + """ + try: + start_time, end_time = times + except: + msg = "ERROR: times should be a tuple or list of two, got: {}".format(times) + WranglerLogger.error(msg) + raise ValueError(msg) + + # If times are strings, convert to int in seconds, else return as ints + if isinstance(start_time, str) and isinstance(end_time, str): + start_time = start_time.strip() + end_time = end_time.strip() + + # If time is given without seconds, add 00 + if len(start_time) <= 5: + start_time += ":00" + if len(end_time) <= 5: + end_time += ":00" + + # Convert times to seconds from midnight (Partride's time storage) + h0, m0, s0 = start_time.split(":") + start_time_sec = int(h0) * 3600 + int(m0) * 60 + int(s0) + + h1, m1, s1 = end_time.split(":") + end_time_sec = int(h1) * 3600 + int(m1) * 60 + int(s1) + + return (start_time_sec, end_time_sec) + + elif isinstance(start_time, int) and isinstance(end_time, int): + return times + + else: + WranglerLogger.error("ERROR: times should be ints or strings") + raise ValueError() + + return (start_time_sec, end_time_sec)
+ + +
[docs]def get_bearing(lat1, lon1, lat2, lon2): + """ + calculate the bearing (forward azimuth) b/w the two points + + returns: bearing in radians + """ + # bearing in degrees + brng = Geodesic.WGS84.Inverse(lat1, lon1, lat2, lon2)["azi1"] + + # convert bearing to radians + brng = math.radians(brng) + + return brng
+ + +
[docs]def offset_point_with_distance_and_bearing(lat, lon, distance, bearing): + """ + Get the new lat long (in degrees) given current point (lat/lon), distance and bearing + + returns: new lat/long + """ + # Earth's radius in meters + radius = 6378137 + + # convert the lat long from degree to radians + lat_radians = math.radians(lat) + lon_radians = math.radians(lon) + + # calculate the new lat long in radians + out_lat_radians = math.asin( + math.sin(lat_radians) * math.cos(distance / radius) + + math.cos(lat_radians) * math.sin(distance / radius) * math.cos(bearing) + ) + + out_lon_radians = lon_radians + math.atan2( + math.sin(bearing) * math.sin(distance / radius) * math.cos(lat_radians), + math.cos(distance / radius) - math.sin(lat_radians) * math.sin(lat_radians), + ) + # convert the new lat long back to degree + out_lat = math.degrees(out_lat_radians) + out_lon = math.degrees(out_lon_radians) + + return (out_lat, out_lon)
+ + +
[docs]def offset_location_reference(location_reference, offset_meters=10): + """ + Creates a new location reference + using the node a and node b of given location reference, + offseting it by 90 degree to the bearing of given location reference + and distance equals to offset_meters + + returns: new location_reference with offset + """ + lon_1 = location_reference[0]["point"][0] + lat_1 = location_reference[0]["point"][1] + lon_2 = location_reference[1]["point"][0] + lat_2 = location_reference[1]["point"][1] + + bearing = get_bearing(lat_1, lon_1, lat_2, lon_2) + # adding 90 degrees (1.57 radians) to the current bearing + bearing = bearing + 1.57 + + new_lat_1, new_lon_1 = offset_point_with_distance_and_bearing( + lat_1, lon_1, offset_meters, bearing + ) + new_lat_2, new_lon_2 = offset_point_with_distance_and_bearing( + lat_2, lon_2, offset_meters, bearing + ) + + out_location_reference = [ + {"sequence": 1, "point": [new_lon_1, new_lat_1]}, + {"sequence": 2, "point": [new_lon_2, new_lat_2]}, + ] + + return out_location_reference
+ + +
[docs]def haversine_distance(origin: list, destination: list, units = "miles"): + """ + Calculates haversine distance between two points + + Args: + origin: lat/lon for point A + destination: lat/lon for point B + units: either "miles" or "meters" + + Returns: string + """ + + lon1, lat1 = origin + lon2, lat2 = destination + radius = 6378137 # meter + + dlat = math.radians(lat2 - lat1) + dlon = math.radians(lon2 - lon1) + a = math.sin(dlat / 2) * math.sin(dlat / 2) + math.cos( + math.radians(lat1) + ) * math.cos(math.radians(lat2)) * math.sin(dlon / 2) * math.sin(dlon / 2) + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) + + d = {"meters": radius * c } # meters + d["miles"] = d["meters"] * 0.000621371 # miles + + return d[units]
+ + +
[docs]def create_unique_shape_id(line_string: LineString): + """ + Creates a unique hash id using the coordinates of the geomtery + + Args: + line_string: Line Geometry as a LineString + + Returns: string + """ + + x1, y1 = list(line_string.coords)[0] # first co-ordinate (A node) + x2, y2 = list(line_string.coords)[-1] # last co-ordinate (B node) + + message = "Geometry {} {} {} {}".format(x1, y1, x2, y2) + unhashed = message.encode("utf-8") + hash = hashlib.md5(unhashed).hexdigest() + + return hash
+ + +
[docs]def create_location_reference_from_nodes(node_a, node_b): + """ + Creates a location reference using the node a and node b coordinates + + Args: + node_a: Node A as Series + node_b: Node B as Series + + """ + out_location_reference = [ + {"sequence": 1, "point": [node_a["X"], node_a["Y"]]}, + {"sequence": 2, "point": [node_b["X"], node_b["Y"]]}, + ] + + return out_location_reference
+ + +
[docs]def create_line_string(location_reference: list): + """ + Creates a geometry as a LineString using location reference + """ + + return LineString([location_reference[0]["point"], location_reference[1]["point"]])
+ +
[docs]def update_df( + base_df: pd.DataFrame, + update_df: pd.DataFrame, + merge_key: str = None, + left_on: str = None, + right_on: str = None, + update_fields: Collection = None, + method: str = "update if found", +): + """ + Updates specific fields of a dataframe with another dataframe using a key column. + + Args: + base_df: DataFrame to be updated + update_df: DataFrame with with updated values + merge_key: column to merge on (i.e. model_link_id). If not specified, must have left_on AND right_on. + left_on: key for base_df. Must also specify right_on. If not specified, must specify merge_key. + right_on: key for update_df. Must also specify left_on. If not specified, must specify merge_key. + update_fields: required list of fields to update values for. Must be columns in update_df. + method: string indicating how the dataframe should be updated. One of: + - "update if found" (default) which will update the values if the update values are not NaN + - "overwrite all" will overwrite the current value with the update value even if it is NaN + - "update nan" will only update values that are currently Nan in the base_df + + Returns: Dataframe with updated values + """ + valid_methods = ["update if found", "overwrite all", "update nan"] + + if method not in valid_methods: + raise ValueError("Specified 'method' was: {} but must be one of: {}".format(method, valid_methods)) + + if update_fields is None: + raise ValueError( + "Must specify which fields to update, None specified." + ) + + if not set(update_fields).issubset(update_df.columns): + raise ValueError( + "Update fields: {} not in update_df: {}".format( + update_fields, update_df.columns + ) + ) + + new_fields = [v for v in update_fields if v not in base_df.columns] + update_fields = list(set(update_fields)-set(new_fields)) + + if new_fields: + WranglerLogger.debug( + "Some update fields: {} not in base_df; adding then as new columns.".format(new_fields) + ) + + if merge_key and left_on or merge_key and right_on: + raise ValueError("Only need a merge_key or right_on and left_on but both specified") + if not merge_key and not (left_on and right_on): + raise ValueError("Need either a merge_key or right_on and left_on but neither fully specified") + + if merge_key: + left_on = merge_key + right_on = merge_key + + if not left_on in base_df.columns: + raise ValueError( + "Merge key: {} not in base_df: {}".format(right_on, base_df.columns) + ) + if not right_on in update_df.columns: + raise ValueError( + "Merge key: {} not in update_df: {}".format(right_on, update_df.columns) + ) + # Actual Process + + if method == "overwrite all": + suffixes = ["-orig", None] + else: + base_df.loc[:, update_fields] = base_df.loc[:, update_fields].replace( + r"^\s*$", np.nan, regex=True + ) + suffixes = [None, "-update"] + # print("base_df2:\n",base_df) + merged_df = base_df.merge( + update_df[update_fields + [(right_on)]], + left_on=left_on, + right_on= right_on, + how="left", + suffixes=suffixes, + ) + # print("merged_df:\n",merged_df) + if method == "overwrite all": + merged_df = merged_df.drop(columns=[c + "-orig" for c in update_fields if c + "-orig" in merged_df.columns]) + merged_df = merged_df[base_df.columns.tolist()] + elif method == "update if found": + #overwrite if the updated field is not Nan + for c in update_fields: + # selects rows where updated value is not NA; + merged_df.loc[~merged_df[c + "-update"].isna(), c] = merged_df.loc[ + ~merged_df[c + "-update"].isna(), c + "-update" + ] + merged_df = merged_df.drop(columns=[c + "-update" for c in update_fields]) + elif method == "update nan": + #overwrite if the base field IS Nan + for c in update_fields: + # print(merged_df.apply(lambda row: row[c+"-update"] if not row[c] else row[c],axis=1)) + merged_df.loc[merged_df[c].isna(), c] = merged_df.loc[ + merged_df[c].isna(), c + "-update" + ] + merged_df = merged_df.drop(columns=[c + "-update" for c in update_fields]) + # print("merged_df-updated:\n",merged_df) + + if new_fields: + merged_df = merged_df.merge( + update_df[new_fields+ [(right_on)]], + left_on=left_on, + right_on= right_on, + how="left", + ) + return merged_df
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/shapely/geometry/linestring/index.html b/_modules/shapely/geometry/linestring/index.html new file mode 100644 index 00000000..8325491c --- /dev/null +++ b/_modules/shapely/geometry/linestring/index.html @@ -0,0 +1,295 @@ + + + + + + shapely.geometry.linestring — Network Wrangler documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for shapely.geometry.linestring

+"""Line strings and related utilities
+"""
+import numpy as np
+
+import shapely
+from shapely.geometry.base import BaseGeometry, JOIN_STYLE
+from shapely.geometry.point import Point
+
+__all__ = ["LineString"]
+
+
+
[docs]class LineString(BaseGeometry): + """ + A geometry type composed of one or more line segments. + + A LineString is a one-dimensional feature and has a non-zero length but + zero area. It may approximate a curve and need not be straight. Unlike a + LinearRing, a LineString is not closed. + + Parameters + ---------- + coordinates : sequence + A sequence of (x, y, [,z]) numeric coordinate pairs or triples, or + an array-like with shape (N, 2) or (N, 3). + Also can be a sequence of Point objects. + + Examples + -------- + Create a LineString with two segments + + >>> a = LineString([[0, 0], [1, 0], [1, 1]]) + >>> a.length + 2.0 + """ + + __slots__ = [] + + def __new__(self, coordinates=None): + if coordinates is None: + # empty geometry + # TODO better constructor + return shapely.from_wkt("LINESTRING EMPTY") + elif isinstance(coordinates, LineString): + if type(coordinates) == LineString: + # return original objects since geometries are immutable + return coordinates + else: + # LinearRing + # TODO convert LinearRing to LineString more directly + coordinates = coordinates.coords + else: + if hasattr(coordinates, "__array__"): + coordinates = np.asarray(coordinates) + if isinstance(coordinates, np.ndarray) and np.issubdtype( + coordinates.dtype, np.number + ): + pass + else: + # check coordinates on points + def _coords(o): + if isinstance(o, Point): + return o.coords[0] + else: + return [float(c) for c in o] + + coordinates = [_coords(o) for o in coordinates] + + if len(coordinates) == 0: + # empty geometry + # TODO better constructor + should shapely.linestrings handle this? + return shapely.from_wkt("LINESTRING EMPTY") + + geom = shapely.linestrings(coordinates) + if not isinstance(geom, LineString): + raise ValueError("Invalid values passed to LineString constructor") + return geom + + @property + def __geo_interface__(self): + return {"type": "LineString", "coordinates": tuple(self.coords)} + +
[docs] def svg(self, scale_factor=1.0, stroke_color=None, opacity=None): + """Returns SVG polyline element for the LineString geometry. + + Parameters + ========== + scale_factor : float + Multiplication factor for the SVG stroke-width. Default is 1. + stroke_color : str, optional + Hex string for stroke color. Default is to use "#66cc99" if + geometry is valid, and "#ff3333" if invalid. + opacity : float + Float number between 0 and 1 for color opacity. Default value is 0.8 + """ + if self.is_empty: + return "<g />" + if stroke_color is None: + stroke_color = "#66cc99" if self.is_valid else "#ff3333" + if opacity is None: + opacity = 0.8 + pnt_format = " ".join(["{},{}".format(*c) for c in self.coords]) + return ( + '<polyline fill="none" stroke="{2}" stroke-width="{1}" ' + 'points="{0}" opacity="{3}" />' + ).format(pnt_format, 2.0 * scale_factor, stroke_color, opacity)
+ + @property + def xy(self): + """Separate arrays of X and Y coordinate values + + Example: + + >>> x, y = LineString([(0, 0), (1, 1)]).xy + >>> list(x) + [0.0, 1.0] + >>> list(y) + [0.0, 1.0] + """ + return self.coords.xy + +
[docs] def offset_curve( + self, + distance, + quad_segs=16, + join_style=JOIN_STYLE.round, + mitre_limit=5.0, + ): + """Returns a LineString or MultiLineString geometry at a distance from + the object on its right or its left side. + + The side is determined by the sign of the `distance` parameter + (negative for right side offset, positive for left side offset). The + resolution of the buffer around each vertex of the object increases + by increasing the `quad_segs` keyword parameter. + + The join style is for outside corners between line segments. Accepted + values are JOIN_STYLE.round (1), JOIN_STYLE.mitre (2), and + JOIN_STYLE.bevel (3). + + The mitre ratio limit is used for very sharp corners. It is the ratio + of the distance from the corner to the end of the mitred offset corner. + When two line segments meet at a sharp angle, a miter join will extend + far beyond the original geometry. To prevent unreasonable geometry, the + mitre limit allows controlling the maximum length of the join corner. + Corners with a ratio which exceed the limit will be beveled. + + Note: the behaviour regarding orientation of the resulting line + depends on the GEOS version. With GEOS < 3.11, the line retains the + same direction for a left offset (positive distance) or has reverse + direction for a right offset (negative distance), and this behaviour + was documented as such in previous Shapely versions. Starting with + GEOS 3.11, the function tries to preserve the orientation of the + original line. + """ + if mitre_limit == 0.0: + raise ValueError("Cannot compute offset from zero-length line segment") + elif not np.isfinite(distance): + raise ValueError("offset_curve distance must be finite") + return shapely.offset_curve(self, distance, quad_segs, join_style, mitre_limit)
+ +
[docs] def parallel_offset( + self, + distance, + side="right", + resolution=16, + join_style=JOIN_STYLE.round, + mitre_limit=5.0, + ): + """ + Alternative method to :meth:`offset_curve` method. + + Older alternative method to the :meth:`offset_curve` method, but uses + ``resolution`` instead of ``quad_segs`` and a ``side`` keyword + ('left' or 'right') instead of sign of the distance. This method is + kept for backwards compatibility for now, but is is recommended to + use :meth:`offset_curve` instead. + """ + if side == "right": + distance *= -1 + return self.offset_curve( + distance, + quad_segs=resolution, + join_style=join_style, + mitre_limit=mitre_limit, + )
+ + +shapely.lib.registry[1] = LineString +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_sources/_generated/network_wrangler.ProjectCard.rst.txt b/_sources/_generated/network_wrangler.ProjectCard.rst.txt new file mode 100644 index 00000000..494b4e5e --- /dev/null +++ b/_sources/_generated/network_wrangler.ProjectCard.rst.txt @@ -0,0 +1,41 @@ +network\_wrangler.ProjectCard +============================= + +.. currentmodule:: network_wrangler + +.. autoclass:: ProjectCard + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~ProjectCard.__init__ + ~ProjectCard.build_link_selection_query + ~ProjectCard.new_roadway + ~ProjectCard.new_transit_right_of_way + ~ProjectCard.parallel_managed_lanes + ~ProjectCard.read + ~ProjectCard.read_wrangler_card + ~ProjectCard.read_yml + ~ProjectCard.roadway_attribute_change + ~ProjectCard.transit_attribute_change + ~ProjectCard.validate_project_card_schema + ~ProjectCard.write + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~ProjectCard.ROADWAY_CATEGORIES + ~ProjectCard.SECONDARY_TRANSIT_CATEGORIES + ~ProjectCard.TRANSIT_CATEGORIES + + \ No newline at end of file diff --git a/_sources/_generated/network_wrangler.RoadwayNetwork.rst.txt b/_sources/_generated/network_wrangler.RoadwayNetwork.rst.txt new file mode 100644 index 00000000..16177f44 --- /dev/null +++ b/_sources/_generated/network_wrangler.RoadwayNetwork.rst.txt @@ -0,0 +1,61 @@ +network\_wrangler.RoadwayNetwork +================================ + +.. currentmodule:: network_wrangler + +.. autoclass:: RoadwayNetwork + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~RoadwayNetwork.__init__ + ~RoadwayNetwork.add_incident_link_data_to_nodes + ~RoadwayNetwork.add_new_roadway_feature_change + ~RoadwayNetwork.addition_map + ~RoadwayNetwork.apply + ~RoadwayNetwork.apply_managed_lane_feature_change + ~RoadwayNetwork.apply_python_calculation + ~RoadwayNetwork.apply_roadway_feature_change + ~RoadwayNetwork.assess_connectivity + ~RoadwayNetwork.build_selection_key + ~RoadwayNetwork.create_dummy_connector_links + ~RoadwayNetwork.create_managed_lane_network + ~RoadwayNetwork.delete_roadway_feature_change + ~RoadwayNetwork.deletion_map + ~RoadwayNetwork.get_managed_lane_node_ids + ~RoadwayNetwork.get_modal_graph + ~RoadwayNetwork.get_modal_links_nodes + ~RoadwayNetwork.get_property_by_time_period_and_group + ~RoadwayNetwork.identify_segment + ~RoadwayNetwork.identify_segment_endpoints + ~RoadwayNetwork.is_network_connected + ~RoadwayNetwork.load_transform_network + ~RoadwayNetwork.network_connection_plot + ~RoadwayNetwork.orig_dest_nodes_foreign_key + ~RoadwayNetwork.ox_graph + ~RoadwayNetwork.path_search + ~RoadwayNetwork.read + ~RoadwayNetwork.roadway_net_to_gdf + ~RoadwayNetwork.select_roadway_features + ~RoadwayNetwork.selection_has_unique_link_id + ~RoadwayNetwork.selection_map + ~RoadwayNetwork.shortest_path + ~RoadwayNetwork.update_distance + ~RoadwayNetwork.validate_link_schema + ~RoadwayNetwork.validate_node_schema + ~RoadwayNetwork.validate_properties + ~RoadwayNetwork.validate_selection + ~RoadwayNetwork.validate_shape_schema + ~RoadwayNetwork.validate_uniqueness + ~RoadwayNetwork.write + + + + + + \ No newline at end of file diff --git a/_sources/_generated/network_wrangler.Scenario.rst.txt b/_sources/_generated/network_wrangler.Scenario.rst.txt new file mode 100644 index 00000000..193bad3c --- /dev/null +++ b/_sources/_generated/network_wrangler.Scenario.rst.txt @@ -0,0 +1,36 @@ +network\_wrangler.Scenario +========================== + +.. currentmodule:: network_wrangler + +.. autoclass:: Scenario + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~Scenario.__init__ + ~Scenario.add_project_card_from_file + ~Scenario.add_project_cards_from_directory + ~Scenario.add_project_cards_from_tags + ~Scenario.applied_project_card_summary + ~Scenario.apply_all_projects + ~Scenario.apply_project + ~Scenario.check_scenario_conflicts + ~Scenario.check_scenario_requisites + ~Scenario.create_base_scenario + ~Scenario.create_scenario + ~Scenario.get_project_names + ~Scenario.order_project_cards + ~Scenario.remove_all_projects + ~Scenario.scenario_summary + + + + + + \ No newline at end of file diff --git a/_sources/_generated/network_wrangler.TransitNetwork.rst.txt b/_sources/_generated/network_wrangler.TransitNetwork.rst.txt new file mode 100644 index 00000000..0d855a64 --- /dev/null +++ b/_sources/_generated/network_wrangler.TransitNetwork.rst.txt @@ -0,0 +1,51 @@ +network\_wrangler.TransitNetwork +================================ + +.. currentmodule:: network_wrangler + +.. autoclass:: TransitNetwork + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~TransitNetwork.__init__ + ~TransitNetwork.apply + ~TransitNetwork.apply_python_calculation + ~TransitNetwork.apply_transit_feature_change + ~TransitNetwork.apply_transit_managed_lane + ~TransitNetwork.check_network_connectivity + ~TransitNetwork.empty + ~TransitNetwork.read + ~TransitNetwork.route_between_nodes + ~TransitNetwork.route_ids_in_routestxt + ~TransitNetwork.select_transit_features + ~TransitNetwork.select_transit_features_by_nodes + ~TransitNetwork.set_roadnet + ~TransitNetwork.shape_ids_in_shapestxt + ~TransitNetwork.stop_ids_in_stopstxt + ~TransitNetwork.transit_net_to_gdf + ~TransitNetwork.trip_ids_in_tripstxt + ~TransitNetwork.validate_feed + ~TransitNetwork.validate_frequencies + ~TransitNetwork.validate_network_keys + ~TransitNetwork.validate_road_network_consistencies + ~TransitNetwork.validate_transit_shapes + ~TransitNetwork.validate_transit_stops + ~TransitNetwork.write + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~TransitNetwork.REQUIRED_FILES + + \ No newline at end of file diff --git a/_sources/_generated/network_wrangler.logger.rst.txt b/_sources/_generated/network_wrangler.logger.rst.txt new file mode 100644 index 00000000..41bb47e3 --- /dev/null +++ b/_sources/_generated/network_wrangler.logger.rst.txt @@ -0,0 +1,29 @@ +network\_wrangler.logger +======================== + +.. automodule:: network_wrangler.logger + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + setupLogging + + + + + + + + + + + + + diff --git a/_sources/_generated/network_wrangler.utils.rst.txt b/_sources/_generated/network_wrangler.utils.rst.txt new file mode 100644 index 00000000..3d864af2 --- /dev/null +++ b/_sources/_generated/network_wrangler.utils.rst.txt @@ -0,0 +1,41 @@ +network\_wrangler.utils +======================= + +.. automodule:: network_wrangler.utils + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + create_line_string + create_location_reference_from_nodes + create_unique_shape_id + get_bearing + haversine_distance + link_df_to_json + make_slug + offset_location_reference + offset_point_with_distance_and_bearing + parse_time_spans + point_df_to_geojson + topological_sort + update_df + + + + + + + + + + + + + diff --git a/_sources/autodoc.rst.txt b/_sources/autodoc.rst.txt new file mode 100644 index 00000000..ea393abc --- /dev/null +++ b/_sources/autodoc.rst.txt @@ -0,0 +1,28 @@ +Wrangler Classes and Functions +==================================== + +.. automodule:: network_wrangler + :no-members: + :no-undoc-members: + :no-inherited-members: + :no-show-inheritance: + + + Base Classes + -------------- +.. autosummary:: + :toctree: _generated + :nosignatures: + + ProjectCard + Scenario + RoadwayNetwork + TransitNetwork + +Utils and Functions +-------------------- +.. autosummary:: + :toctree: _generated + + utils + logger diff --git a/_sources/contributing.md.txt b/_sources/contributing.md.txt new file mode 100644 index 00000000..716ae88e --- /dev/null +++ b/_sources/contributing.md.txt @@ -0,0 +1,37 @@ +# Contributing + +## Development Workflow + +1. Create [an issue](https://github.com/wsp-sag/network_wrangler/issues) for any features/bugs that you are working on. +2. Develop comprehensive tests. +3. Modify code including inline documentation such that it passes *all* tests (not just your new ones) +4. Lint code to PEP8 using a tool like `black` +5. Fill out information in the [pull request template](https://github.com/wsp-sag/network_wrangler/blob/master/.github/.github/pull_request_template.md) +6. Submit pull requests to the `develop` branch. +7. Core developer will review your pull request and suggest changes. +8. After requested changes are complete, core developer will sign off on pull-request merge. + +## Documentation + +Documentation is produced by Sphinx and can be run by executing the following from the `/docs` folder: + +```bash +make html +``` + +## Roadmap + +- [Issue List](https://github.com/wsp-sag/network_wrangler/issues) +- [To Do List](todo) + +## Testing + +Tests and test data reside in the `/tests` directory. To run: + +```bash +pytest +``` + +## Continuous Integration + +Continuous integration is set up in [Travis CI](https://travis-ci.org/wsp-sag/network_wrangler). diff --git a/_sources/design.md.txt b/_sources/design.md.txt new file mode 100644 index 00000000..0661ea84 --- /dev/null +++ b/_sources/design.md.txt @@ -0,0 +1,15 @@ +# Design + + +## Atomic Parts + +NetworkWrangler deals with four primary atomic parts: + +**1. [`Scenario`](_generated/network_wrangler.Scenario.html)** objects describe a Roadway Network, Transit Network, and collection of Projects. Scenarios manage the addition and construction of projects on the network via projct cards. Scenarios can be based on or tiered from other scenarios. + +**2. [`RoadwayNetwork`](_generated/network_wrangler.RoadwayNetwork.html)** objects stores information about roadway nodes, directed links between nodes, and the shapes of links (note that the same shape can be shared between two or more links). Network Wrangler reads/writes roadway network objects from/to three files: `links.json`, `shapes.geojson`, and `nodes.geojson`. Their data is stored as GeoDataFrames in the object. + +**3. [`TransitNetwork`](_generated/network_wrangler.TransitNetwork.html)** objects contain information about stops, routes, trips, shapes, stoptimes, and frequencies. Network Wrangler reads/writes transit network information from/to gtfs csv files and stores them as DataFrames within a +`Partridge` `feed` object. Transit networks can be associated with Roadway networks. + +**4.[`ProjectCard`](_generated/network_wrangler.ProjectCard.html)** objects store infromation (including metadata) about changes to the network. Network Wtanglr reads project cards from .yml files validates them, and manages them within a `Scenario` object. diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 00000000..a08d2b38 --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,29 @@ +.. Network Wrangler documentation master file, created by + sphinx-quickstart on Mon May 13 22:23:51 2019. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Network Wrangler's documentation! +============================================ + +.. note:: This documentation is based on the `generic_agency `_ branch + +Objectives +------------ + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + design + autodoc + contributing + todo + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/_sources/todo.rst.txt b/_sources/todo.rst.txt new file mode 100644 index 00000000..b663e413 --- /dev/null +++ b/_sources/todo.rst.txt @@ -0,0 +1,7 @@ +To Do List +=========== + +.. todolist:: + + +.. todo:: diff --git a/_static/_sphinx_javascript_frameworks_compat.js b/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 00000000..81415803 --- /dev/null +++ b/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 00000000..cfc60b86 --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,921 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/css/badge_only.css b/_static/css/badge_only.css new file mode 100644 index 00000000..c718cee4 --- /dev/null +++ b/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff b/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff2 b/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff b/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff2 b/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/_static/css/fonts/fontawesome-webfont.eot b/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/_static/css/fonts/fontawesome-webfont.svg b/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000..855c845e --- /dev/null +++ b/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_static/css/fonts/fontawesome-webfont.ttf b/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/_static/css/fonts/fontawesome-webfont.woff b/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/_static/css/fonts/fontawesome-webfont.woff2 b/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/_static/css/fonts/lato-bold-italic.woff b/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff differ diff --git a/_static/css/fonts/lato-bold-italic.woff2 b/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/_static/css/fonts/lato-bold.woff b/_static/css/fonts/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/_static/css/fonts/lato-bold.woff differ diff --git a/_static/css/fonts/lato-bold.woff2 b/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff2 differ diff --git a/_static/css/fonts/lato-normal-italic.woff b/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff differ diff --git a/_static/css/fonts/lato-normal-italic.woff2 b/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/_static/css/fonts/lato-normal.woff b/_static/css/fonts/lato-normal.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/_static/css/fonts/lato-normal.woff differ diff --git a/_static/css/fonts/lato-normal.woff2 b/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/_static/css/fonts/lato-normal.woff2 differ diff --git a/_static/css/theme.css b/_static/css/theme.css new file mode 100644 index 00000000..19a446a0 --- /dev/null +++ b/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 00000000..d06a71d7 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 00000000..c066c69a --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,14 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'dirhtml', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 00000000..a858a410 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/graphviz.css b/_static/graphviz.css new file mode 100644 index 00000000..8d81c02e --- /dev/null +++ b/_static/graphviz.css @@ -0,0 +1,19 @@ +/* + * graphviz.css + * ~~~~~~~~~~~~ + * + * Sphinx stylesheet -- graphviz extension. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +img.graphviz { + border: 0; + max-width: 100%; +} + +object.graphviz { + max-width: 100%; +} diff --git a/_static/jquery.js b/_static/jquery.js new file mode 100644 index 00000000..c4c6022f --- /dev/null +++ b/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/_static/js/html5shiv.min.js b/_static/js/html5shiv.min.js new file mode 100644 index 00000000..cd1c674f --- /dev/null +++ b/_static/js/html5shiv.min.js @@ -0,0 +1,4 @@ +/** +* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/_static/js/theme.js b/_static/js/theme.js new file mode 100644 index 00000000..1fddb6ee --- /dev/null +++ b/_static/js/theme.js @@ -0,0 +1 @@ +!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 00000000..d96755fd Binary files /dev/null and b/_static/minus.png differ diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 00000000..7107cec9 Binary files /dev/null and b/_static/plus.png differ diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 00000000..84ab3030 --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #9C6500 } /* Comment.Preproc */ +.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #E40000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #008400 } /* Generic.Inserted */ +.highlight .go { color: #717171 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #687822 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0000FF } /* Name.Function */ +.highlight .nl { color: #767600 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #666666 } /* Literal.Number.Bin */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #0000FF } /* Name.Function.Magic */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .vm { color: #19177C } /* Name.Variable.Magic */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/searchtools.js b/_static/searchtools.js new file mode 100644 index 00000000..97d56a74 --- /dev/null +++ b/_static/searchtools.js @@ -0,0 +1,566 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = docUrlRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = docUrlRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms) + ); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + `Search finished, found ${resultCount} page(s) matching the search query.` + ); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent !== undefined) return docContent.textContent; + console.warn( + "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + /** + * execute search (requires search index to be loaded) + */ + query: (query) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + // array of [docname, title, anchor, descr, score, filename] + let results = []; + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + results.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id] of foundEntries) { + let score = Math.round(100 * queryLower.length / entry.length) + results.push([ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // lookup as object + objectTerms.forEach((term) => + results.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); + + // now sort the results by score (in opposite order of appearance, since the + // display function below uses pop() to retrieve items) and then + // alphabetically + results.sort((a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; + }); + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + results = results.reverse(); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord) && !terms[word]) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord) && !titleTerms[word]) + arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); + }); + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) + fileMap.get(file).push(word); + else fileMap.set(file, [word]); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords) => { + const text = Search.htmlToText(htmlText); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js new file mode 100644 index 00000000..aae669d7 --- /dev/null +++ b/_static/sphinx_highlight.js @@ -0,0 +1,144 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + parent.insertBefore( + span, + parent.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(SphinxHighlight.highlightSearchWords); +_ready(SphinxHighlight.initEscapeListener); diff --git a/autodoc/index.html b/autodoc/index.html new file mode 100644 index 00000000..f1aadd23 --- /dev/null +++ b/autodoc/index.html @@ -0,0 +1,159 @@ + + + + + + + Wrangler Classes and Functions — Network Wrangler documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Wrangler Classes and Functions

+
+

Base Classes

+
+ + + + + + + + + + + + + + + +

ProjectCard

Representation of a Project Card

Scenario

Holds information about a scenario.

RoadwayNetwork

Representation of a Roadway Network.

TransitNetwork

Representation of a Transit Network.

+
+

Utils and Functions

+ + + + + + + + + +

utils

logger

+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2020-2022, Metropolitan Council, Metropolitan Transportation Commission.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/contributing/index.html b/contributing/index.html new file mode 100644 index 00000000..695a2872 --- /dev/null +++ b/contributing/index.html @@ -0,0 +1,160 @@ + + + + + + + Contributing — Network Wrangler documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Contributing

+
+

Development Workflow

+
    +
  1. Create an issue for any features/bugs that you are working on.

  2. +
  3. Develop comprehensive tests.

  4. +
  5. Modify code including inline documentation such that it passes all tests (not just your new ones)

  6. +
  7. Lint code to PEP8 using a tool like black

  8. +
  9. Fill out information in the pull request template

  10. +
  11. Submit pull requests to the develop branch.

  12. +
  13. Core developer will review your pull request and suggest changes.

  14. +
  15. After requested changes are complete, core developer will sign off on pull-request merge.

  16. +
+
+
+

Documentation

+

Documentation is produced by Sphinx and can be run by executing the following from the /docs folder:

+
make html
+
+
+
+
+

Roadmap

+ +
+
+

Testing

+

Tests and test data reside in the /tests directory. To run:

+
pytest
+
+
+
+
+

Continuous Integration

+

Continuous integration is set up in Travis CI.

+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2020-2022, Metropolitan Council, Metropolitan Transportation Commission.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/design/index.html b/design/index.html new file mode 100644 index 00000000..25708597 --- /dev/null +++ b/design/index.html @@ -0,0 +1,127 @@ + + + + + + + Design — Network Wrangler documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Design

+
+

Atomic Parts

+

NetworkWrangler deals with four primary atomic parts:

+

1. Scenario objects describe a Roadway Network, Transit Network, and collection of Projects. Scenarios manage the addition and construction of projects on the network via projct cards. Scenarios can be based on or tiered from other scenarios.

+

2. RoadwayNetwork objects stores information about roadway nodes, directed links between nodes, and the shapes of links (note that the same shape can be shared between two or more links). Network Wrangler reads/writes roadway network objects from/to three files: links.json, shapes.geojson, and nodes.geojson. Their data is stored as GeoDataFrames in the object.

+

3. TransitNetwork objects contain information about stops, routes, trips, shapes, stoptimes, and frequencies. Network Wrangler reads/writes transit network information from/to gtfs csv files and stores them as DataFrames within a +Partridge feed object. Transit networks can be associated with Roadway networks.

+

4.ProjectCard objects store infromation (including metadata) about changes to the network. Network Wtanglr reads project cards from .yml files validates them, and manages them within a Scenario object.

+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2020-2022, Metropolitan Council, Metropolitan Transportation Commission.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/genindex/index.html b/genindex/index.html new file mode 100644 index 00000000..f56e6128 --- /dev/null +++ b/genindex/index.html @@ -0,0 +1,898 @@ + + + + + + Index — Network Wrangler documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Index

+ +
+ _ + | A + | B + | C + | D + | E + | F + | G + | H + | I + | K + | L + | M + | N + | O + | P + | R + | S + | T + | U + | V + | W + | X + +
+

_

+ + +
+ +

A

+ + + +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

H

+ + + +
+ +

I

+ + + +
+ +

K

+ + +
+ +

L

+ + + +
+ +

M

+ + + +
+ +

N

+ + + +
+ +

O

+ + + +
+ +

P

+ + + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + + +
+ +

V

+ + + +
+ +

W

+ + + +
+ +

X

+ + + +
+ + + +
+
+
+ +
+ +
+

© Copyright 2020-2022, Metropolitan Council, Metropolitan Transportation Commission.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 00000000..607f14d8 --- /dev/null +++ b/index.html @@ -0,0 +1,155 @@ + + + + + + + Welcome to Network Wrangler’s documentation! — Network Wrangler documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Welcome to Network Wrangler’s documentation!

+
+

Note

+

This documentation is based on the generic_agency branch

+
+
+

Objectives

+ +
+
+
+

Indices and tables

+ +
+ + +
+
+
+ +
+ +
+

© Copyright 2020-2022, Metropolitan Council, Metropolitan Transportation Commission.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 00000000..6f1c5226 Binary files /dev/null and b/objects.inv differ diff --git a/py-modindex/index.html b/py-modindex/index.html new file mode 100644 index 00000000..c105130d --- /dev/null +++ b/py-modindex/index.html @@ -0,0 +1,136 @@ + + + + + + Python Module Index — Network Wrangler documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Python Module Index

+ +
+ n +
+ + + + + + + + + + + + + +
 
+ n
+ network_wrangler +
    + network_wrangler.logger +
    + network_wrangler.utils +
+ + +
+
+
+ +
+ +
+

© Copyright 2020-2022, Metropolitan Council, Metropolitan Transportation Commission.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/search/index.html b/search/index.html new file mode 100644 index 00000000..931e131c --- /dev/null +++ b/search/index.html @@ -0,0 +1,126 @@ + + + + + + Search — Network Wrangler documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2020-2022, Metropolitan Council, Metropolitan Transportation Commission.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 00000000..b47994f8 --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"docnames": ["_generated/network_wrangler.ProjectCard", "_generated/network_wrangler.RoadwayNetwork", "_generated/network_wrangler.Scenario", "_generated/network_wrangler.TransitNetwork", "_generated/network_wrangler.logger", "_generated/network_wrangler.utils", "autodoc", "contributing", "design", "index", "todo"], "filenames": ["_generated/network_wrangler.ProjectCard.rst", "_generated/network_wrangler.RoadwayNetwork.rst", "_generated/network_wrangler.Scenario.rst", "_generated/network_wrangler.TransitNetwork.rst", "_generated/network_wrangler.logger.rst", "_generated/network_wrangler.utils.rst", "autodoc.rst", "contributing.md", "design.md", "index.rst", "todo.rst"], "titles": ["network_wrangler.ProjectCard", "network_wrangler.RoadwayNetwork", "network_wrangler.Scenario", "network_wrangler.TransitNetwork", "network_wrangler.logger", "network_wrangler.utils", "Wrangler Classes and Functions", "Contributing", "Design", "Welcome to Network Wrangler\u2019s documentation!", "To Do List"], "terms": {"class": [0, 1, 2, 3, 5, 9], "attribute_dictonari": 0, "sourc": [0, 1, 2, 3, 4, 5], "base": [0, 1, 2, 3, 5, 8, 9], "object": [0, 1, 2, 3, 5, 8], "represent": [0, 1, 2, 3, 5], "project": [0, 1, 2, 3, 5, 8, 10], "card": [0, 1, 2, 3, 8, 10], "__dict__": [0, 2], "dictionari": [0, 1, 2, 3, 10], "attribut": [0, 1, 3], "valid": [0, 1, 2, 3, 5, 8, 10], "boolean": [0, 1, 2, 3], "indic": [0, 1, 2, 3, 5], "data": [0, 1, 5, 7, 8], "conform": [0, 1], "schema": [0, 1], "__init__": [0, 1, 2, 3, 10], "constructor": [0, 1, 2, 3], "paramet": [0, 1, 2, 3, 4, 5], "nest": 0, "method": [0, 1, 2, 3, 5, 10], "static": [0, 1, 2, 3], "build_link_selection_queri": 0, "select": [0, 1, 3], "unique_link_id": [0, 1], "mode": [0, 1, 2], "drive_access": [0, 1], "ignor": [0, 1, 5], "One": [0, 5], "line": [0, 1, 5, 10], "descript": 0, "todo": 0, "239": 0, "238": 0, "return": [0, 1, 2, 3, 5], "usag": [0, 1, 2, 3], "new_roadwai": 0, "probabl": 0, "delet": [0, 1], "read": [0, 1, 2, 3, 8, 10], "new": [0, 1, 3, 5, 7], "roadwai": [0, 1, 2, 3, 8], "arg": [0, 2, 5], "store": [0, 1, 2, 8], "new_transit_right_of_wai": 0, "transit": [0, 1, 2, 3, 8, 10], "dedic": 0, "right": [0, 1, 5, 10], "wai": 0, "parallel_managed_lan": 0, "parallel": [0, 1, 5], "manag": [0, 1, 8], "lane": [0, 1], "card_filenam": [0, 2], "true": [0, 1, 2, 3, 4, 5, 10], "The": [0, 3, 4, 5, 10], "path": [0, 1, 2, 3], "file": [0, 1, 2, 3, 4, 8], "should": [0, 1, 5], "default": [0, 1, 2, 3, 5, 10], "read_wrangler_card": 0, "w_card_filenam": 0, "wrangler": [0, 8], "yaml": 0, "front": 0, "matter": 0, "python": [0, 1, 2, 3, 5], "code": [0, 1, 3, 5, 7], "type": [0, 1, 2, 3, 5], "dict": [0, 1, 2, 3, 5], "where": [0, 1, 2, 3], "i": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10], "read_yml": 0, "normal": [0, 5], "defin": [0, 1, 5], "locat": [0, 2, 4, 5, 10], "roadway_attribute_chang": 0, "chang": [0, 1, 2, 3, 7, 8], "transit_attribute_chang": 0, "servic": 0, "validate_project_card_schema": 0, "card_schema_filenam": 0, "project_card": [0, 2], "json": [0, 1, 5, 8], "test": [0, 1, 5, 9, 10], "evalu": [0, 1], "bool": [0, 1, 2, 3, 5], "yml": [0, 2, 8], "against": [0, 3], "write": [0, 1, 2, 3, 8], "out_filenam": 0, "none": [0, 1, 2, 3, 4, 5], "If": [0, 1, 2, 5], "provid": [0, 1, 2, 3, 5], "current": [0, 5], "directori": [0, 7], "us": [0, 1, 3, 5, 7], "name": [0, 1, 2, 3, 5], "filenam": [0, 1, 2, 3], "roadway_categori": 0, "properti": [0, 1, 3, 5], "add": [0, 1, 2, 4, 5], "calcul": [0, 1, 5], "secondary_transit_categori": 0, "transit_categori": 0, "node": [1, 2, 3, 5, 8, 10], "link": [1, 2, 3, 8, 10], "shape": [1, 2, 3, 5, 8, 10], "node_foreign_kei": [1, 5], "link_foreign_kei": 1, "shape_foreign_kei": 1, "unique_link_kei": 1, "unique_node_kei": 1, "unique_node_id": 1, "cr": 1, "kwarg": [1, 2, 5], "network": [1, 2, 3, 8], "typic": [1, 2, 3, 5], "exampl": [1, 2, 3, 5, 10], "net": 1, "link_filenam": [1, 2], "my_link_fil": 1, "node_filenam": [1, 2], "my_node_fil": 1, "shape_filenam": [1, 2], "my_shape_fil": 1, "shape_id": [1, 3], "my_select": 1, "35e": 1, "A": [1, 2, 3, 5], "osm_node_id": 1, "961117623": 1, "start": [1, 5], "search": [1, 2, 9], "segment": [1, 5], "b": [1, 2, 3, 5], "2564047368": 1, "select_roadway_featur": 1, "my_chang": 1, "exist": 1, "1": [1, 5, 8], "set": [1, 4, 5, 7], "2": [1, 2, 5, 8], "0": [1, 5], "my_net": 1, "apply_roadway_feature_chang": 1, "ml_net": 1, "create_managed_lane_network": [1, 10], "in_plac": [1, 3], "fals": [1, 2, 3, 5], "is_network_connect": [1, 10], "drive": 1, "_": [1, 5], "disconnected_nod": 1, "assess_connect": 1, "walk": 1, "ignore_end_nod": 1, "my_out_prefix": 1, "my_dir": 1, "nodes_df": 1, "geodatafram": [1, 3, 8], "links_df": 1, "includ": [1, 2, 7, 8], "end": [1, 5], "associ": [1, 3, 8, 10], "shapes_df": 1, "detail": [1, 4], "coordin": [1, 5], "refer": [1, 3, 5, 10], "system": 1, "espg": 1, "number": [1, 5], "int": [1, 3, 5], "variabl": 1, "tabl": 1, "str": [1, 2, 3, 5], "list": [1, 2, 3, 5, 7, 9], "foreign": [1, 3], "kei": [1, 3, 5], "uniqu": [1, 5], "each": [1, 2, 3, 5], "modes_to_network_link_vari": 1, "map": [1, 3], "modes_to_network_nodes_vari": 1, "managed_lanes_node_id_scalar": 1, "scalar": [1, 3], "valu": [1, 2, 3, 5], "ad": [1, 2, 3, 5], "primari": [1, 8], "correspond": [1, 5], "managed_lanes_link_id_scalar": 1, "managed_lanes_required_attribut": 1, "must": [1, 3, 5], "specifi": [1, 2, 5], "keep_same_attributes_ml_and_gp": 1, "copi": [1, 5], "from": [1, 2, 3, 5, 7, 8], "gener": [1, 3], "purpos": [1, 5], "case": 1, "thei": [1, 2, 10], "ar": [1, 2, 3, 5, 7, 10], "made": 1, "repeatedli": 1, "add_incident_link_data_to_nod": 1, "link_vari": 1, "model_node_id": [1, 3], "go": [1, 4], "datafram": [1, 3, 5, 8], "assess": 1, "connect": [1, 3], "thi": [1, 2, 3, 5, 9, 10], "rather": [1, 5], "than": [1, 5], "self": [1, 3, 5], "column": [1, 5], "incid": 1, "length": [1, 5], "n": [1, 5], "out": [1, 2, 3, 5, 7, 10], "add_new_roadway_feature_chang": [1, 10], "featur": [1, 3, 5, 7], "also": [1, 5], "addition_map": 1, "show": 1, "which": [1, 3, 5], "appli": [1, 2, 3, 5], "project_card_dictionari": [1, 2, 3], "wrapper": [1, 3], "apply_managed_lane_feature_chang": [1, 10], "link_idx": 1, "lndice": 1, "all": [1, 3, 5, 7], "whether": [1, 2, 3], "updat": [1, 3, 5], "decid": [1, 10], "connector": [1, 10], "info": [1, 4, 10], "when": [1, 3, 5, 10], "more": [1, 3, 5, 8, 10], "specif": [1, 2, 5, 10], "apply_python_calcul": [1, 3], "pycod": [1, 3], "execut": [1, 3, 7], "inform": [1, 2, 3, 7, 8], "pass": [1, 2, 3, 7], "graph": [1, 3, 5, 10], "disconnect": 1, "subgraph": 1, "describ": [1, 5, 8], "member": [1, 5], "one": [1, 3, 5], "bike": 1, "strai": 1, "singleton": 1, "tupl": [1, 5], "osmnx": 1, "flavor": 1, "networkx": [1, 3], "digraph": [1, 3], "build_selection_kei": 1, "selection_dict": 1, "combin": [1, 3, 5], "queri": 1, "id": [1, 3, 5], "two": [1, 5, 8], "you": [1, 7], "selection_dictonari": 1, "serv": 1, "create_dummy_connector_link": 1, "ml_df": 1, "access_lan": 1, "egress_lan": 1, "access_roadwai": 1, "ml_access": 1, "egress_roadwai": 1, "access_name_prefix": 1, "access": [1, 5], "dummi": 1, "egress_name_prefix": 1, "egress": 1, "creat": [1, 2, 3, 4, 5, 7], "between": [1, 3, 5, 8], "gp_df": 1, "roadai": 1, "prefix": [1, 3], "keep_additional_attributes_ml_and_gp": 1, "separ": [1, 5], "look": [1, 2], "instanc": [1, 2, 3], "found": [1, 5], "addit": [1, 5, 8], "want": 1, "leav": 1, "some": 1, "other": [1, 5, 8], "requir": [1, 3, 5], "integ": [1, 5], "origin": [1, 5, 10], "make": [1, 2, 3, 5, 7, 10], "rigor": [1, 10], "delete_roadway_feature_chang": 1, "ignore_miss": 1, "get": [1, 4, 5], "onli": [1, 2, 5, 10], "warn": 1, "about": [1, 2, 8], "miss": [1, 2], "fail": [1, 2], "deletion_map": 1, "get_managed_lane_node_id": 1, "nodes_list": 1, "4500000": 1, "transform": 1, "237": 1, "what": [1, 2], "get_modal_graph": 1, "bike_access": 1, "bu": 1, "bus_onli": 1, "rail": 1, "rail_onli": 1, "walk_access": 1, "determin": [1, 5], "strongli": 1, "vertex": [1, 5], "reachabl": 1, "everi": [1, 5], "standard": [1, 3, 5], "get_modal_links_nod": [1, 10], "kept": [1, 5], "For": 1, "both": [1, 3, 5], "filter": [1, 10], "now": [1, 5, 10], "we": [1, 10], "don": [1, 10], "t": [1, 4, 10], "becaus": [1, 5, 10], "mark": 1, "have": [1, 2, 3, 5], "issu": [1, 3, 7], "discuss": 1, "http": [1, 2, 5], "github": 1, "com": [1, 5], "wsp": 1, "sag": 1, "145": 1, "modal_nodes_df": 1, "mode_node_vari": 1, "get_property_by_time_period_and_group": 1, "prop": 1, "time_period": 1, "categori": 1, "default_return": 1, "seri": [1, 3, 5], "group": 1, "time": [1, 5, 10], "period": 1, "e": [1, 2, 5], "16": [1, 5], "00": 1, "19": 1, "option": [1, 2, 5], "sov": 1, "order": [1, 2, 5], "hov3": 1, "hov2": 1, "panda": [1, 5], "identify_seg": 1, "o_id": 1, "d_id": 1, "endpoint": 1, "up": [1, 4, 7], "candid": 1, "otherwis": [1, 5], "ram": 1, "hog": 1, "could": [1, 5], "result": [1, 5], "odd": 1, "shortest": 1, "segment_vari": 1, "keep": 1, "identify_segment_endpoint": 1, "min_connecting_link": 1, "10": [1, 5], "min_dist": 1, "max_link_devi": 1, "consid": [1, 5, 10], "cach": [1, 10], "take": [1, 10], "long": [1, 5, 10], "load_transform_network": 1, "4326": 1, "validate_schema": 1, "disk": 1, "them": [1, 5, 8], "network_connection_plot": 1, "g": [1, 5], "disconnected_subgraph_nod": 1, "plot": 1, "check": [1, 2, 3, 5], "fig": 1, "ax": [1, 5], "orig_dest_nodes_foreign_kei": 1, "whatev": 1, "u": 1, "v": 1, "ab": 1, "noth": 1, "assum": 1, "a_id": 1, "b_id": 1, "ox_graph": 1, "model_link_id": [1, 5], "an": [1, 2, 3, 5, 7], "doesn": 1, "like": [1, 5, 7], "arrai": [1, 5], "so": [1, 5], "remov": [1, 5], "certain": 1, "fill": [1, 3, 7, 10], "do": [1, 7, 9], "too": 1, "link_df": 1, "field": [1, 5], "referenc": 1, "multidigraph": [1, 3], "path_search": 1, "candidate_links_df": 1, "weight_column": 1, "weight_factor": 1, "search_breadth": 1, "5": [1, 5, 10], "max_search_breadth": 1, "candidate_link": 1, "part": [1, 3, 5, 9], "foreigh": 1, "destin": [1, 5], "weight": 1, "iter": 1, "multipli": 1, "find": [1, 3], "fast": [1, 2, 10], "bike_nod": 1, "walk_nod": 1, "10000000": 1, "locationrefer": 1, "distanc": [1, 5], "transit_access": 1, "maxspe": 1, "onewai": 1, "ref": 1, "segment_id": 1, "ft": 1, "assign": 1, "counti": 1, "full": [1, 5], "skip": 1, "speed": 1, "turn": [1, 10], "off": [1, 7, 10], "roadway_net_to_gdf": [1, 10], "roadway_net": 1, "param": [1, 5], "export": [1, 5], "much": [1, 10], "sophist": [1, 3, 10], "attach": [1, 10], "search_mod": 1, "force_search": 1, "sp_weight_factor": 1, "satisfi": [1, 5], "criteria": 1, "match": [1, 2], "condit": 1, "osm": 1, "share": [1, 8], "street": 1, "model": 1, "osm_model_link_id": 1, "1234": 1, "shstid": 1, "4321": 1, "regex": 1, "facil": 1, "etc": 1, "main": 1, "st": 1, "least": 1, "direct": [1, 5, 8], "perform": 1, "even": [1, 5], "same": [1, 5, 8], "previou": [1, 5], "multipl": [1, 3, 5], "discourag": 1, "meander": 1, "here": 1, "defaul": [1, 3], "selection_has_unique_link_id": 1, "selection_dictionari": 1, "contain": [1, 3, 5, 8], "identifi": [1, 3], "selection_map": 1, "selected_link_idx": 1, "candidate_link_idx": 1, "selected_links_idx": 1, "candidate_links_idx": 1, "rout": [1, 3, 8], "shortest_path": [1, 3], "graph_links_df": 1, "100": 1, "df": [1, 3, 5], "ani": [1, 2, 3, 5, 7], "four": [1, 8], "nx": [1, 3], "update_dist": 1, "use_shap": 1, "unit": [1, 5], "mile": [1, 5], "network_vari": 1, "overwrit": [1, 2, 5], "inplac": 1, "either": [1, 3, 5], "straight": [1, 5], "avail": 1, "portion": 1, "centroid": [1, 5], "entir": 1, "crow": 1, "fly": 1, "meter": [1, 5], "nan": [1, 5], "validate_link_schema": 1, "schema_loc": 1, "roadway_network_link": 1, "output": [1, 3, 5], "validate_node_schema": 1, "node_fil": 1, "roadway_network_nod": 1, "validate_properti": 1, "ignore_exist": 1, "require_existing_for_chang": 1, "command": 1, "sure": [1, 2, 3], "isn": 1, "theproject": 1, "dictonari": 1, "validate_select": 1, "selection_requir": 1, "whetther": 1, "minimum": [1, 4, 5], "validate_shape_schema": 1, "shape_fil": 1, "roadway_network_shap": 1, "validate_uniqu": 1, "confirm": 1, "met": 1, "were": [1, 2, 3], "save": [1, 3], "base_scenario": 2, "hold": 2, "my_base_scenario": 2, "road_net": [2, 3], "roadwaynetwork": [2, 3, 8, 9, 10], "stpaul_link_fil": 2, "stpaul_node_fil": 2, "stpaul_shape_fil": 2, "transit_net": 2, "transitnetwork": [2, 8, 9, 10], "stpaul_dir": 2, "3_multiple_roadway_attribute_chang": 2, "multiple_chang": 2, "4_simple_managed_lan": 2, "project_card_directori": 2, "o": 2, "join": [2, 5], "project_cards_list": 2, "projectcard": [2, 8, 9], "my_scenario": 2, "create_scenario": 2, "check_scenario_requisit": 2, "apply_all_project": 2, "scenario_summari": 2, "applied_project": 2, "been": [2, 3], "ordered_project_card": 2, "prerequisit": 2, "prerequist": 2, "corequisit": 2, "conflict": 2, "requisites_check": 2, "co": 2, "pre": 2, "requisit": 2, "conflicts_check": 2, "has_requisite_error": 2, "has_conflict_error": 2, "prerequisites_sort": 2, "sort": [2, 5], "first": [2, 5], "": [2, 3, 5, 10], "add_project_card_from_fil": 2, "project_card_filenam": 2, "tag": 2, "add_project_cards_from_directori": 2, "folder": [2, 3, 7], "glob_search": 2, "applic": [2, 5], "glob": 2, "style": [2, 5], "road": [2, 3], "doc": [2, 7], "org": 2, "librari": [2, 3], "html": [2, 7], "add_project_cards_from_tag": 2, "applied_project_card_summari": 2, "summari": 2, "apply_project": 2, "p": 2, "check_scenario_conflict": 2, "wa": [2, 3, 5], "success": 2, "error": 2, "create_base_scenario": 2, "base_shape_nam": 2, "base_link_nam": 2, "base_node_nam": 2, "roadway_dir": 2, "transit_dir": 2, "card_directori": 2, "validate_project_card": 2, "user": 2, "get_project_nam": 2, "order_project_card": 2, "remove_all_project": 2, "project_detail": 2, "outfil": 2, "high": 2, "level": [2, 4], "text": [2, 5], "open": 2, "append": 2, "w": [2, 4, 5], "string": [2, 5], "feed": [3, 8], "config": 3, "shapes_foreign_kei": 3, "stops_foreign_kei": 3, "id_scalar": 3, "import": [3, 5], "wr": 3, "stpaul": 3, "r": [3, 4], "home": [3, 10], "jovyan": 3, "work": [3, 7, 10], "tc": 3, "partridg": [3, 8], "dotdict": 3, "feed_path": 3, "validated_frequ": 3, "frequenc": [3, 8], "validated_road_network_consist": 3, "ha": [3, 5], "stop": [3, 8], "necessari": 3, "required_fil": 3, "its": [3, 5, 10], "own": [3, 10], "thing": [3, 10], "apply_transit_feature_chang": 3, "trip_id": 3, "pd": 3, "place": [3, 5], "apply_transit_managed_lan": 3, "node_id": 3, "check_network_connect": 3, "empti": [3, 5, 10], "rtype": 3, "shape_model_node_id": 3, "100000000": 3, "gtf": [3, 8], "Will": 3, "route_between_nod": 3, "complet": [3, 5, 7], "route_ids_in_routestxt": 3, "wherev": 3, "route_id": 3, "occur": 3, "txt": 3, "okai": 3, "select_transit_featur": 3, "trip": [3, 8], "select_transit_features_by_nod": 3, "require_al": 3, "come": 3, "travers": 3, "set_roadnet": 3, "graph_shap": 3, "graph_stop": 3, "validate_consist": 3, "shape_ids_in_shapestxt": 3, "stop_ids_in_stopstxt": 3, "stop_id": 3, "transit_net_to_gdf": [3, 10], "given": [3, 5], "trip_ids_in_tripstxt": 3, "validate_fe": 3, "sinc": [3, 5], "lazili": 3, "load": [3, 5], "actual": 3, "repres": [3, 5], "relationship": [3, 5], "edg": [3, 5], "validate_frequ": 3, "zero": [3, 5], "state": 3, "outcom": 3, "validate_network_kei": 3, "present": 3, "validate_road_network_consist": 3, "validate_transit_shap": 3, "validate_transit_stop": 3, "agenc": 3, "stop_tim": 3, "function": [4, 5, 9], "setuplog": 4, "info_log_filenam": 4, "debug_log_filenam": 4, "log_to_consol": 4, "wranglerlogg": 4, "debug": 4, "log": 4, "consol": 4, "ters": 4, "just": [4, 7], "give": 4, "bare": 4, "veri": [4, 5], "noisi": 4, "geodes": 5, "f": 5, "solv": 5, "problem": 5, "arcdirect": 5, "lat1": 5, "lon1": 5, "azi1": 5, "a12": 5, "outmask": 5, "1929": 5, "term": 5, "spheric": 5, "arc": 5, "latitud": 5, "point": 5, "degre": 5, "longitud": 5, "azimuth": 5, "second": 5, "mask": 5, "comput": 5, "lat2": 5, "lon2": 5, "azi2": 5, "s12": 5, "entri": [5, 10], "arcdirectlin": 5, "cap": 5, "3979": 5, "geodesiclin": 5, "capabl": 5, "3": [5, 8, 10], "distance_in": 5, "allow": 5, "directlin": 5, "invers": 5, "inverselin": 5, "inves": 5, "along": 5, "polygon": 5, "polylin": 5, "polygonarea": 5, "instead": 5, "32671": 5, "abov": 5, "area": 5, "16400": 5, "512": 5, "cap_al": 5, "31": 5, "cap_c1": 5, "cap_c1p": 5, "cap_c2": 5, "4": [5, 8], "cap_c3": 5, "8": [5, 10], "cap_c4": 5, "cap_mask": 5, "cap_non": 5, "1025": 5, "2051": 5, "input": 5, "No": 5, "geodesicscal": 5, "8197": 5, "scale": 5, "m12": 5, "m21": 5, "geographiclib_geodesic_ord": 5, "6": 5, "128": 5, "264": 5, "long_unrol": 5, "32768": 5, "unrol": 5, "reduc": 5, "rang": 5, "180d": 5, "out_al": 5, "32640": 5, "out_mask": 5, "65408": 5, "reducedlength": 5, "4101": 5, "wgs84": 5, "geographiclib": 5, "equatori": 5, "radiu": 5, "readonli": 5, "flatten": 5, "maxit1_": 5, "20": 5, "maxit2_": 5, "83": 5, "na1_": 5, "na2_": 5, "na3_": 5, "na3x_": 5, "nc1_": 5, "nc1p_": 5, "nc2_": 5, "nc3_": 5, "nc3x_": 5, "15": 5, "nc4_": 5, "nc4x_": 5, "21": 5, "tiny_": 5, "4916681462400413e": 5, "154": 5, "tol0_": 5, "220446049250313e": 5, "tol1_": 5, "440892098500626e": 5, "14": 5, "tol2_": 5, "4901161193847656e": 5, "08": 5, "tolb_": 5, "308722450212111e": 5, "24": 5, "xthresh_": 5, "05": 5, "linestr": 5, "basegeometri": 5, "geometri": 5, "compos": 5, "dimension": 5, "non": 5, "It": 5, "mai": 5, "approxim": 5, "curv": 5, "need": 5, "unlik": 5, "linear": 5, "close": 5, "sequenc": 5, "x": 5, "y": 5, "z": 5, "numer": 5, "pair": 5, "tripl": 5, "can": [5, 7, 8], "almost_equ": 5, "decim": 5, "equal": 5, "deprec": 5, "version": 5, "confus": 5, "equals_exact": 5, "compon": 5, "possibl": 5, "1e": 5, "buffer": 5, "quad_seg": 5, "cap_styl": 5, "round": 5, "join_styl": 5, "mitre_limit": 5, "single_sid": 5, "within": [5, 8], "posit": 5, "produc": [5, 7], "dilat": 5, "neg": 5, "eros": 5, "small": 5, "sometim": 5, "tidi": 5, "float": 5, "around": 5, "resolut": 5, "angl": 5, "fillet": 5, "buffercapstyl": 5, "squar": 5, "flat": 5, "circular": 5, "see": 5, "rectangular": 5, "while": 5, "involv": 5, "width": 5, "bufferjoinstyl": 5, "mitr": 5, "bevel": 5, "midpoint": 5, "touch": 5, "singl": 5, "depend": 5, "limit": 5, "ratio": 5, "sharp": 5, "corner": 5, "offset": 5, "meet": 5, "miter": 5, "extend": 5, "To": [5, 7, 9], "prevent": 5, "unreason": 5, "control": 5, "maximum": 5, "exce": 5, "side": 5, "sign": [5, 7], "left": 5, "hand": 5, "regular": 5, "alwai": 5, "forc": 5, "equival": 5, "cap_flat": 5, "quadseg": 5, "alia": 5, "note": [5, 8], "strictli": 5, "wkt": 5, "gon": 5, "approx": 5, "circl": 5, "1365484905459": 5, "141513801144": 5, "triangl": 5, "exterior": 5, "coord": 5, "els": 5, "contains_properli": 5, "common": 5, "boundari": 5, "document": 5, "covered_bi": 5, "cover": 5, "cross": 5, "differ": 5, "grid_siz": 5, "disjoint": 5, "unitless": 5, "dwithin": 5, "topolog": 5, "toler": 5, "comparison": 5, "absolut": 5, "geometrytyp": 5, "hausdorff_dist": 5, "hausdorff": 5, "interpol": 5, "taken": 5, "measur": 5, "revers": 5, "index": [5, 9], "handl": 5, "clamp": 5, "interpret": 5, "fraction": 5, "line_interpolate_point": 5, "intersect": 5, "line_locate_point": 5, "nearest": 5, "convert": 5, "form": 5, "canon": 5, "ring": 5, "multi": 5, "consist": 5, "multilinestr": 5, "offset_curv": 5, "increas": 5, "keyword": 5, "outsid": 5, "accept": 5, "far": 5, "beyond": 5, "behaviour": 5, "regard": 5, "orient": 5, "geo": 5, "With": 5, "11": [5, 10], "retain": 5, "tri": 5, "preserv": 5, "overlap": 5, "parallel_offset": 5, "altern": 5, "older": 5, "backward": 5, "compat": 5, "recommend": 5, "point_on_surfac": 5, "guarante": 5, "cheapli": 5, "representative_point": 5, "relat": 5, "de": 5, "9im": 5, "matrix": 5, "relate_pattern": 5, "pattern": 5, "interior": 5, "unchang": 5, "is_ccw": 5, "clockwis": 5, "max_segment_length": 5, "vertic": 5, "longer": 5, "evenli": 5, "subdivid": 5, "densifi": 5, "unmodifi": 5, "array_lik": 5, "greater": 5, "simplifi": 5, "preserve_topologi": 5, "dougla": 5, "peucker": 5, "algorithm": 5, "unless": 5, "topologi": 5, "invalid": 5, "svg": 5, "scale_factor": 5, "stroke_color": 5, "opac": 5, "element": 5, "factor": 5, "stroke": 5, "hex": 5, "color": 5, "66cc99": 5, "ff3333": 5, "symmetric_differ": 5, "symmetr": 5, "union": 5, "lower": 5, "dimens": 5, "bound": 5, "collect": [5, 8], "null": 5, "region": 5, "minx": 5, "mini": 5, "maxx": 5, "maxi": 5, "geometr": 5, "center": 5, "convex_hul": 5, "convex": 5, "hull": 5, "less": 5, "three": [5, 8], "multipoint": 5, "triangular": 5, "imagin": 5, "elast": 5, "band": 5, "stretch": 5, "coordinatesequ": 5, "envelop": 5, "figur": 5, "geom_typ": 5, "has_z": 5, "is_clos": 5, "d": 5, "is_empti": 5, "is_r": 5, "is_simpl": 5, "simpl": 5, "mean": 5, "is_valid": 5, "definit": 5, "sub": 5, "minimum_clear": 5, "move": 5, "minimum_rotated_rectangl": 5, "rotat": 5, "rectangl": 5, "enclos": 5, "constrain": 5, "degener": 5, "oriented_envelop": 5, "wkb": 5, "wkb_hex": 5, "xy": 5, "create_line_str": 5, "location_refer": 5, "create_location_reference_from_nod": 5, "node_a": 5, "node_b": 5, "create_unique_shape_id": 5, "line_str": 5, "hash": 5, "geomteri": 5, "get_bear": 5, "bear": 5, "forward": 5, "radian": 5, "haversine_dist": 5, "haversin": 5, "lat": 5, "lon": 5, "link_df_to_json": 5, "modifi": [5, 7], "geoff": 5, "boe": 5, "geoffbo": 5, "2015": 5, "geojson": [5, 8], "make_slug": 5, "delimit": 5, "slug": 5, "offset_location_refer": 5, "offset_met": 5, "90": 5, "offset_point_with_distance_and_bear": 5, "parse_time_span": 5, "pars": 5, "span": 5, "midnight": 5, "point_df_to_geojson": 5, "author": 5, "topological_sort": 5, "adjacency_list": 5, "visited_list": 5, "acycl": 5, "update_df": 5, "base_df": 5, "merge_kei": 5, "left_on": 5, "right_on": 5, "update_field": 5, "anoth": 5, "merg": [5, 7], "AND": 5, "how": 5, "bug": 7, "comprehens": 7, "inlin": 7, "your": 7, "ones": 7, "lint": 7, "pep8": 7, "tool": 7, "black": 7, "pull": 7, "request": 7, "templat": 7, "submit": 7, "branch": [7, 9], "core": 7, "review": 7, "suggest": 7, "after": 7, "sphinx": 7, "run": 7, "follow": 7, "resid": 7, "pytest": 7, "travi": 7, "ci": 7, "networkwrangl": 8, "deal": 8, "scenario": [8, 9], "construct": 8, "via": 8, "projct": 8, "tier": 8, "Their": 8, "stoptim": 8, "csv": 8, "infrom": 8, "metadata": 8, "wtanglr": 8, "generic_ag": 9, "design": 9, "atom": 9, "network_wrangl": [9, 10], "util": 9, "contribut": 9, "develop": 9, "workflow": 9, "roadmap": 9, "continu": 9, "integr": 9, "modul": 9, "page": 9, "runner": 10, "py": 10, "docstr": 10, "25": 10, "12": 10}, "objects": {"": [[6, 0, 0, "-", "network_wrangler"]], "network_wrangler": [[0, 1, 1, "", "ProjectCard"], [1, 1, 1, "", "RoadwayNetwork"], [2, 1, 1, "", "Scenario"], [3, 1, 1, "", "TransitNetwork"], [4, 0, 0, "-", "logger"], [5, 0, 0, "-", "utils"]], "network_wrangler.ProjectCard": [[0, 2, 1, "", "ROADWAY_CATEGORIES"], [0, 2, 1, "", "SECONDARY_TRANSIT_CATEGORIES"], [0, 2, 1, "", "TRANSIT_CATEGORIES"], [0, 2, 1, "", "__dict__"], [0, 3, 1, "", "__init__"], [0, 3, 1, "", "build_link_selection_query"], [0, 3, 1, "", "new_roadway"], [0, 3, 1, "", "new_transit_right_of_way"], [0, 3, 1, "", "parallel_managed_lanes"], [0, 3, 1, "", "read"], [0, 3, 1, "", "read_wrangler_card"], [0, 3, 1, "", "read_yml"], [0, 3, 1, "", "roadway_attribute_change"], [0, 3, 1, "", "transit_attribute_change"], [0, 2, 1, "", "valid"], [0, 3, 1, "", "validate_project_card_schema"], [0, 3, 1, "", "write"]], "network_wrangler.RoadwayNetwork": [[1, 3, 1, "", "__init__"], [1, 3, 1, "", "add_incident_link_data_to_nodes"], [1, 3, 1, "", "add_new_roadway_feature_change"], [1, 3, 1, "", "addition_map"], [1, 3, 1, "", "apply"], [1, 3, 1, "", "apply_managed_lane_feature_change"], [1, 3, 1, "", "apply_python_calculation"], [1, 3, 1, "", "apply_roadway_feature_change"], [1, 3, 1, "", "assess_connectivity"], [1, 3, 1, "", "build_selection_key"], [1, 3, 1, "", "create_dummy_connector_links"], [1, 3, 1, "", "create_managed_lane_network"], [1, 2, 1, "", "crs"], [1, 3, 1, "", "delete_roadway_feature_change"], [1, 3, 1, "", "deletion_map"], [1, 3, 1, "", "get_managed_lane_node_ids"], [1, 3, 1, "", "get_modal_graph"], [1, 3, 1, "", "get_modal_links_nodes"], [1, 3, 1, "", "get_property_by_time_period_and_group"], [1, 3, 1, "", "identify_segment"], [1, 3, 1, "", "identify_segment_endpoints"], [1, 3, 1, "", "is_network_connected"], [1, 2, 1, "", "keep_same_attributes_ml_and_gp"], [1, 2, 1, "", "link_foreign_key"], [1, 2, 1, "", "links_df"], [1, 3, 1, "", "load_transform_network"], [1, 2, 1, "", "managed_lanes_link_id_scalar"], [1, 2, 1, "", "managed_lanes_node_id_scalar"], [1, 2, 1, "", "managed_lanes_required_attributes"], [1, 2, 1, "", "modes_to_network_link_variables"], [1, 2, 1, "", "modes_to_network_nodes_variables"], [1, 3, 1, "", "network_connection_plot"], [1, 2, 1, "", "node_foreign_key"], [1, 2, 1, "", "nodes_df"], [1, 3, 1, "", "orig_dest_nodes_foreign_key"], [1, 3, 1, "", "ox_graph"], [1, 3, 1, "", "path_search"], [1, 3, 1, "", "read"], [1, 3, 1, "", "roadway_net_to_gdf"], [1, 3, 1, "", "select_roadway_features"], [1, 3, 1, "", "selection_has_unique_link_id"], [1, 3, 1, "", "selection_map"], [1, 2, 1, "", "selections"], [1, 2, 1, "", "shape_foreign_key"], [1, 2, 1, "", "shapes_df"], [1, 3, 1, "", "shortest_path"], [1, 2, 1, "", "unique_link_ids"], [1, 2, 1, "", "unique_node_ids"], [1, 3, 1, "", "update_distance"], [1, 3, 1, "", "validate_link_schema"], [1, 3, 1, "", "validate_node_schema"], [1, 3, 1, "", "validate_properties"], [1, 3, 1, "", "validate_selection"], [1, 3, 1, "", "validate_shape_schema"], [1, 3, 1, "", "validate_uniqueness"], [1, 3, 1, "", "write"]], "network_wrangler.Scenario": [[2, 3, 1, "", "__init__"], [2, 3, 1, "", "add_project_card_from_file"], [2, 3, 1, "", "add_project_cards_from_directory"], [2, 3, 1, "", "add_project_cards_from_tags"], [2, 3, 1, "", "applied_project_card_summary"], [2, 2, 1, "", "applied_projects"], [2, 3, 1, "", "apply_all_projects"], [2, 3, 1, "", "apply_project"], [2, 2, 1, "", "base_scenario"], [2, 3, 1, "", "check_scenario_conflicts"], [2, 3, 1, "", "check_scenario_requisites"], [2, 2, 1, "", "conflicts"], [2, 2, 1, "", "conflicts_checked"], [2, 2, 1, "", "corequisites"], [2, 3, 1, "", "create_base_scenario"], [2, 3, 1, "", "create_scenario"], [2, 3, 1, "", "get_project_names"], [2, 2, 1, "", "has_conflict_error"], [2, 2, 1, "", "has_requisite_error"], [2, 3, 1, "", "order_project_cards"], [2, 2, 1, "", "ordered_project_cards"], [2, 2, 1, "", "prerequisites"], [2, 2, 1, "", "prerequisites_sorted"], [2, 2, 1, "id0", "project_cards"], [2, 3, 1, "", "remove_all_projects"], [2, 2, 1, "", "requisites_checked"], [2, 2, 1, "", "road_net"], [2, 3, 1, "", "scenario_summary"], [2, 2, 1, "", "transit_net"]], "network_wrangler.TransitNetwork": [[3, 2, 1, "id0", "REQUIRED_FILES"], [3, 3, 1, "", "__init__"], [3, 3, 1, "", "apply"], [3, 3, 1, "", "apply_python_calculation"], [3, 3, 1, "", "apply_transit_feature_change"], [3, 3, 1, "", "apply_transit_managed_lane"], [3, 3, 1, "", "check_network_connectivity"], [3, 2, 1, "", "config"], [3, 3, 1, "", "empty"], [3, 2, 1, "", "feed"], [3, 2, 1, "", "feed_path"], [3, 2, 1, "", "graph"], [3, 2, 1, "", "id_scalar"], [3, 3, 1, "", "read"], [3, 2, 1, "", "road_net"], [3, 3, 1, "", "route_between_nodes"], [3, 3, 1, "", "route_ids_in_routestxt"], [3, 3, 1, "", "select_transit_features"], [3, 3, 1, "", "select_transit_features_by_nodes"], [3, 3, 1, "", "set_roadnet"], [3, 3, 1, "", "shape_ids_in_shapestxt"], [3, 2, 1, "", "shapes_foreign_key"], [3, 3, 1, "", "stop_ids_in_stopstxt"], [3, 2, 1, "", "stops_foreign_key"], [3, 3, 1, "", "transit_net_to_gdf"], [3, 3, 1, "", "trip_ids_in_tripstxt"], [3, 3, 1, "", "validate_feed"], [3, 3, 1, "", "validate_frequencies"], [3, 3, 1, "", "validate_network_keys"], [3, 3, 1, "", "validate_road_network_consistencies"], [3, 3, 1, "", "validate_transit_shapes"], [3, 3, 1, "", "validate_transit_stops"], [3, 2, 1, "", "validated_frequencies"], [3, 2, 1, "", "validated_road_network_consistency"], [3, 3, 1, "", "write"]], "network_wrangler.logger": [[4, 4, 1, "", "setupLogging"]], "network_wrangler.utils": [[5, 1, 1, "", "Geodesic"], [5, 1, 1, "", "LineString"], [5, 4, 1, "", "create_line_string"], [5, 4, 1, "", "create_location_reference_from_nodes"], [5, 4, 1, "", "create_unique_shape_id"], [5, 4, 1, "", "get_bearing"], [5, 4, 1, "", "haversine_distance"], [5, 4, 1, "", "link_df_to_json"], [5, 4, 1, "", "make_slug"], [5, 4, 1, "", "offset_location_reference"], [5, 4, 1, "", "offset_point_with_distance_and_bearing"], [5, 4, 1, "", "parse_time_spans"], [5, 4, 1, "", "point_df_to_geojson"], [5, 4, 1, "", "topological_sort"], [5, 4, 1, "", "update_df"]], "network_wrangler.utils.Geodesic": [[5, 2, 1, "", "ALL"], [5, 2, 1, "", "AREA"], [5, 2, 1, "", "AZIMUTH"], [5, 3, 1, "", "ArcDirect"], [5, 3, 1, "", "ArcDirectLine"], [5, 2, 1, "", "CAP_ALL"], [5, 2, 1, "", "CAP_C1"], [5, 2, 1, "", "CAP_C1p"], [5, 2, 1, "", "CAP_C2"], [5, 2, 1, "", "CAP_C3"], [5, 2, 1, "", "CAP_C4"], [5, 2, 1, "", "CAP_MASK"], [5, 2, 1, "", "CAP_NONE"], [5, 2, 1, "", "DISTANCE"], [5, 2, 1, "", "DISTANCE_IN"], [5, 3, 1, "", "Direct"], [5, 3, 1, "", "DirectLine"], [5, 2, 1, "", "EMPTY"], [5, 2, 1, "", "GEODESICSCALE"], [5, 2, 1, "", "GEOGRAPHICLIB_GEODESIC_ORDER"], [5, 3, 1, "", "Inverse"], [5, 3, 1, "", "InverseLine"], [5, 2, 1, "", "LATITUDE"], [5, 2, 1, "", "LONGITUDE"], [5, 2, 1, "", "LONG_UNROLL"], [5, 3, 1, "", "Line"], [5, 2, 1, "", "OUT_ALL"], [5, 2, 1, "", "OUT_MASK"], [5, 3, 1, "", "Polygon"], [5, 2, 1, "", "REDUCEDLENGTH"], [5, 2, 1, "", "STANDARD"], [5, 2, 1, "", "WGS84"], [5, 2, 1, "", "a"], [5, 2, 1, "", "f"], [5, 2, 1, "", "maxit1_"], [5, 2, 1, "", "maxit2_"], [5, 2, 1, "", "nA1_"], [5, 2, 1, "", "nA2_"], [5, 2, 1, "", "nA3_"], [5, 2, 1, "", "nA3x_"], [5, 2, 1, "", "nC1_"], [5, 2, 1, "", "nC1p_"], [5, 2, 1, "", "nC2_"], [5, 2, 1, "", "nC3_"], [5, 2, 1, "", "nC3x_"], [5, 2, 1, "", "nC4_"], [5, 2, 1, "", "nC4x_"], [5, 2, 1, "", "tiny_"], [5, 2, 1, "", "tol0_"], [5, 2, 1, "", "tol1_"], [5, 2, 1, "", "tol2_"], [5, 2, 1, "", "tolb_"], [5, 2, 1, "", "xthresh_"]], "network_wrangler.utils.LineString": [[5, 3, 1, "", "almost_equals"], [5, 5, 1, "", "area"], [5, 5, 1, "", "boundary"], [5, 5, 1, "", "bounds"], [5, 3, 1, "", "buffer"], [5, 5, 1, "", "centroid"], [5, 3, 1, "", "contains"], [5, 3, 1, "", "contains_properly"], [5, 5, 1, "", "convex_hull"], [5, 5, 1, "", "coords"], [5, 3, 1, "", "covered_by"], [5, 3, 1, "", "covers"], [5, 3, 1, "", "crosses"], [5, 3, 1, "", "difference"], [5, 3, 1, "", "disjoint"], [5, 3, 1, "", "distance"], [5, 3, 1, "", "dwithin"], [5, 5, 1, "", "envelope"], [5, 3, 1, "", "equals"], [5, 3, 1, "", "equals_exact"], [5, 5, 1, "", "geom_type"], [5, 3, 1, "", "geometryType"], [5, 5, 1, "", "has_z"], [5, 3, 1, "", "hausdorff_distance"], [5, 3, 1, "", "interpolate"], [5, 3, 1, "", "intersection"], [5, 3, 1, "", "intersects"], [5, 5, 1, "", "is_closed"], [5, 5, 1, "", "is_empty"], [5, 5, 1, "", "is_ring"], [5, 5, 1, "", "is_simple"], [5, 5, 1, "", "is_valid"], [5, 5, 1, "", "length"], [5, 3, 1, "", "line_interpolate_point"], [5, 3, 1, "", "line_locate_point"], [5, 5, 1, "", "minimum_clearance"], [5, 5, 1, "", "minimum_rotated_rectangle"], [5, 3, 1, "", "normalize"], [5, 3, 1, "", "offset_curve"], [5, 5, 1, "", "oriented_envelope"], [5, 3, 1, "", "overlaps"], [5, 3, 1, "", "parallel_offset"], [5, 3, 1, "", "point_on_surface"], [5, 3, 1, "", "project"], [5, 3, 1, "", "relate"], [5, 3, 1, "", "relate_pattern"], [5, 3, 1, "", "representative_point"], [5, 3, 1, "", "reverse"], [5, 3, 1, "", "segmentize"], [5, 3, 1, "", "simplify"], [5, 3, 1, "", "svg"], [5, 3, 1, "", "symmetric_difference"], [5, 3, 1, "", "touches"], [5, 5, 1, "", "type"], [5, 3, 1, "", "union"], [5, 3, 1, "", "within"], [5, 5, 1, "", "wkb"], [5, 5, 1, "", "wkb_hex"], [5, 5, 1, "", "wkt"], [5, 5, 1, "", "xy"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:attribute", "3": "py:method", "4": "py:function", "5": "py:property"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "attribute", "Python attribute"], "3": ["py", "method", "Python method"], "4": ["py", "function", "Python function"], "5": ["py", "property", "Python property"]}, "titleterms": {"network_wrangl": [0, 1, 2, 3, 4, 5], "projectcard": 0, "roadwaynetwork": 1, "todo": [1, 3, 10], "scenario": 2, "transitnetwork": 3, "logger": 4, "util": [5, 6], "wrangler": [6, 9], "class": 6, "function": 6, "base": 6, "contribut": 7, "develop": 7, "workflow": 7, "document": [7, 9], "roadmap": 7, "test": 7, "continu": 7, "integr": 7, "design": 8, "atom": 8, "part": 8, "welcom": 9, "network": 9, "": 9, "object": 9, "content": 9, "indic": 9, "tabl": 9, "To": 10, "do": 10, "list": 10}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.todo": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1, "sphinx": 58}, "alltitles": {"network_wrangler.ProjectCard": [[0, "network-wrangler-projectcard"]], "network_wrangler.RoadwayNetwork": [[1, "network-wrangler-roadwaynetwork"]], "Todo": [[1, "id1"], [1, "id2"], [1, "id3"], [1, "id4"], [1, "id5"], [1, "id6"], [1, "id7"], [3, "id1"], [3, "id2"], [3, "id3"], [10, null], [10, null], [10, null], [10, null], [10, null], [10, null], [10, null], [10, null], [10, null], [10, null]], "network_wrangler.Scenario": [[2, "network-wrangler-scenario"]], "network_wrangler.TransitNetwork": [[3, "network-wrangler-transitnetwork"]], "network_wrangler.logger": [[4, "module-network_wrangler.logger"]], "network_wrangler.utils": [[5, "module-network_wrangler.utils"]], "Wrangler Classes and Functions": [[6, "module-network_wrangler"]], "Base Classes": [[6, "base-classes"]], "Utils and Functions": [[6, "utils-and-functions"]], "Contributing": [[7, "contributing"]], "Development Workflow": [[7, "development-workflow"]], "Documentation": [[7, "documentation"]], "Roadmap": [[7, "roadmap"]], "Testing": [[7, "testing"]], "Continuous Integration": [[7, "continuous-integration"]], "Design": [[8, "design"]], "Atomic Parts": [[8, "atomic-parts"]], "Welcome to Network Wrangler\u2019s documentation!": [[9, "welcome-to-network-wrangler-s-documentation"]], "Objectives": [[9, "objectives"]], "Contents:": [[9, null]], "Indices and tables": [[9, "indices-and-tables"]], "To Do List": [[10, "to-do-list"]]}, "indexentries": {"projectcard (class in network_wrangler)": [[0, "network_wrangler.ProjectCard"]], "roadway_categories (network_wrangler.projectcard attribute)": [[0, "network_wrangler.ProjectCard.ROADWAY_CATEGORIES"]], "secondary_transit_categories (network_wrangler.projectcard attribute)": [[0, "network_wrangler.ProjectCard.SECONDARY_TRANSIT_CATEGORIES"]], "transit_categories (network_wrangler.projectcard attribute)": [[0, "network_wrangler.ProjectCard.TRANSIT_CATEGORIES"]], "__dict__ (network_wrangler.projectcard attribute)": [[0, "network_wrangler.ProjectCard.__dict__"]], "__init__() (network_wrangler.projectcard method)": [[0, "network_wrangler.ProjectCard.__init__"]], "build_link_selection_query() (network_wrangler.projectcard static method)": [[0, "network_wrangler.ProjectCard.build_link_selection_query"]], "new_roadway() (network_wrangler.projectcard method)": [[0, "network_wrangler.ProjectCard.new_roadway"]], "new_transit_right_of_way() (network_wrangler.projectcard method)": [[0, "network_wrangler.ProjectCard.new_transit_right_of_way"]], "parallel_managed_lanes() (network_wrangler.projectcard method)": [[0, "network_wrangler.ProjectCard.parallel_managed_lanes"]], "read() (network_wrangler.projectcard static method)": [[0, "network_wrangler.ProjectCard.read"]], "read_wrangler_card() (network_wrangler.projectcard static method)": [[0, "network_wrangler.ProjectCard.read_wrangler_card"]], "read_yml() (network_wrangler.projectcard static method)": [[0, "network_wrangler.ProjectCard.read_yml"]], "roadway_attribute_change() (network_wrangler.projectcard method)": [[0, "network_wrangler.ProjectCard.roadway_attribute_change"]], "transit_attribute_change() (network_wrangler.projectcard method)": [[0, "network_wrangler.ProjectCard.transit_attribute_change"]], "valid (network_wrangler.projectcard attribute)": [[0, "network_wrangler.ProjectCard.valid"]], "validate_project_card_schema() (network_wrangler.projectcard static method)": [[0, "network_wrangler.ProjectCard.validate_project_card_schema"]], "write() (network_wrangler.projectcard method)": [[0, "network_wrangler.ProjectCard.write"]], "roadwaynetwork (class in network_wrangler)": [[1, "network_wrangler.RoadwayNetwork"]], "__init__() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.__init__"]], "add_incident_link_data_to_nodes() (network_wrangler.roadwaynetwork static method)": [[1, "network_wrangler.RoadwayNetwork.add_incident_link_data_to_nodes"]], "add_new_roadway_feature_change() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.add_new_roadway_feature_change"]], "addition_map() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.addition_map"]], "apply() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.apply"]], "apply_managed_lane_feature_change() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.apply_managed_lane_feature_change"]], "apply_python_calculation() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.apply_python_calculation"]], "apply_roadway_feature_change() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.apply_roadway_feature_change"]], "assess_connectivity() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.assess_connectivity"]], "build_selection_key() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.build_selection_key"]], "create_dummy_connector_links() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.create_dummy_connector_links"]], "create_managed_lane_network() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.create_managed_lane_network"]], "crs (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.crs"]], "delete_roadway_feature_change() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.delete_roadway_feature_change"]], "deletion_map() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.deletion_map"]], "get_managed_lane_node_ids() (network_wrangler.roadwaynetwork static method)": [[1, "network_wrangler.RoadwayNetwork.get_managed_lane_node_ids"]], "get_modal_graph() (network_wrangler.roadwaynetwork static method)": [[1, "network_wrangler.RoadwayNetwork.get_modal_graph"]], "get_modal_links_nodes() (network_wrangler.roadwaynetwork static method)": [[1, "network_wrangler.RoadwayNetwork.get_modal_links_nodes"]], "get_property_by_time_period_and_group() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.get_property_by_time_period_and_group"]], "identify_segment() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.identify_segment"]], "identify_segment_endpoints() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.identify_segment_endpoints"]], "is_network_connected() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.is_network_connected"]], "keep_same_attributes_ml_and_gp (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.keep_same_attributes_ml_and_gp"]], "link_foreign_key (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.link_foreign_key"]], "links_df (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.links_df"]], "load_transform_network() (network_wrangler.roadwaynetwork static method)": [[1, "network_wrangler.RoadwayNetwork.load_transform_network"]], "managed_lanes_link_id_scalar (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.managed_lanes_link_id_scalar"]], "managed_lanes_node_id_scalar (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.managed_lanes_node_id_scalar"]], "managed_lanes_required_attributes (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.managed_lanes_required_attributes"]], "modes_to_network_link_variables (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.modes_to_network_link_variables"]], "modes_to_network_nodes_variables (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.modes_to_network_nodes_variables"]], "network_connection_plot() (network_wrangler.roadwaynetwork static method)": [[1, "network_wrangler.RoadwayNetwork.network_connection_plot"]], "node_foreign_key (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.node_foreign_key"]], "nodes_df (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.nodes_df"]], "orig_dest_nodes_foreign_key() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.orig_dest_nodes_foreign_key"]], "ox_graph() (network_wrangler.roadwaynetwork static method)": [[1, "network_wrangler.RoadwayNetwork.ox_graph"]], "path_search() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.path_search"]], "read() (network_wrangler.roadwaynetwork static method)": [[1, "network_wrangler.RoadwayNetwork.read"]], "roadway_net_to_gdf() (network_wrangler.roadwaynetwork static method)": [[1, "network_wrangler.RoadwayNetwork.roadway_net_to_gdf"]], "select_roadway_features() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.select_roadway_features"]], "selection_has_unique_link_id() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.selection_has_unique_link_id"]], "selection_map() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.selection_map"]], "selections (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.selections"]], "shape_foreign_key (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.shape_foreign_key"]], "shapes_df (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.shapes_df"]], "shortest_path() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.shortest_path"]], "unique_link_ids (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.unique_link_ids"]], "unique_node_ids (network_wrangler.roadwaynetwork attribute)": [[1, "network_wrangler.RoadwayNetwork.unique_node_ids"]], "update_distance() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.update_distance"]], "validate_link_schema() (network_wrangler.roadwaynetwork static method)": [[1, "network_wrangler.RoadwayNetwork.validate_link_schema"]], "validate_node_schema() (network_wrangler.roadwaynetwork static method)": [[1, "network_wrangler.RoadwayNetwork.validate_node_schema"]], "validate_properties() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.validate_properties"]], "validate_selection() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.validate_selection"]], "validate_shape_schema() (network_wrangler.roadwaynetwork static method)": [[1, "network_wrangler.RoadwayNetwork.validate_shape_schema"]], "validate_uniqueness() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.validate_uniqueness"]], "write() (network_wrangler.roadwaynetwork method)": [[1, "network_wrangler.RoadwayNetwork.write"]], "scenario (class in network_wrangler)": [[2, "network_wrangler.Scenario"]], "__init__() (network_wrangler.scenario method)": [[2, "network_wrangler.Scenario.__init__"]], "add_project_card_from_file() (network_wrangler.scenario method)": [[2, "network_wrangler.Scenario.add_project_card_from_file"]], "add_project_cards_from_directory() (network_wrangler.scenario method)": [[2, "network_wrangler.Scenario.add_project_cards_from_directory"]], "add_project_cards_from_tags() (network_wrangler.scenario method)": [[2, "network_wrangler.Scenario.add_project_cards_from_tags"]], "applied_project_card_summary() (network_wrangler.scenario method)": [[2, "network_wrangler.Scenario.applied_project_card_summary"]], "applied_projects (network_wrangler.scenario attribute)": [[2, "network_wrangler.Scenario.applied_projects"]], "apply_all_projects() (network_wrangler.scenario method)": [[2, "network_wrangler.Scenario.apply_all_projects"]], "apply_project() (network_wrangler.scenario method)": [[2, "network_wrangler.Scenario.apply_project"]], "base_scenario (network_wrangler.scenario attribute)": [[2, "network_wrangler.Scenario.base_scenario"]], "check_scenario_conflicts() (network_wrangler.scenario method)": [[2, "network_wrangler.Scenario.check_scenario_conflicts"]], "check_scenario_requisites() (network_wrangler.scenario method)": [[2, "network_wrangler.Scenario.check_scenario_requisites"]], "conflicts (network_wrangler.scenario attribute)": [[2, "network_wrangler.Scenario.conflicts"]], "conflicts_checked (network_wrangler.scenario attribute)": [[2, "network_wrangler.Scenario.conflicts_checked"]], "corequisites (network_wrangler.scenario attribute)": [[2, "network_wrangler.Scenario.corequisites"]], "create_base_scenario() (network_wrangler.scenario static method)": [[2, "network_wrangler.Scenario.create_base_scenario"]], "create_scenario() (network_wrangler.scenario static method)": [[2, "network_wrangler.Scenario.create_scenario"]], "get_project_names() (network_wrangler.scenario method)": [[2, "network_wrangler.Scenario.get_project_names"]], "has_conflict_error (network_wrangler.scenario attribute)": [[2, "network_wrangler.Scenario.has_conflict_error"]], "has_requisite_error (network_wrangler.scenario attribute)": [[2, "network_wrangler.Scenario.has_requisite_error"]], "order_project_cards() (network_wrangler.scenario method)": [[2, "network_wrangler.Scenario.order_project_cards"]], "ordered_project_cards (network_wrangler.scenario attribute)": [[2, "network_wrangler.Scenario.ordered_project_cards"]], "prerequisites (network_wrangler.scenario attribute)": [[2, "network_wrangler.Scenario.prerequisites"]], "prerequisites_sorted (network_wrangler.scenario attribute)": [[2, "network_wrangler.Scenario.prerequisites_sorted"]], "project_cards (network_wrangler.scenario attribute)": [[2, "id0"], [2, "network_wrangler.Scenario.project_cards"]], "remove_all_projects() (network_wrangler.scenario method)": [[2, "network_wrangler.Scenario.remove_all_projects"]], "requisites_checked (network_wrangler.scenario attribute)": [[2, "network_wrangler.Scenario.requisites_checked"]], "road_net (network_wrangler.scenario attribute)": [[2, "network_wrangler.Scenario.road_net"]], "scenario_summary() (network_wrangler.scenario method)": [[2, "network_wrangler.Scenario.scenario_summary"]], "transit_net (network_wrangler.scenario attribute)": [[2, "network_wrangler.Scenario.transit_net"]], "required_files (network_wrangler.transitnetwork attribute)": [[3, "id0"], [3, "network_wrangler.TransitNetwork.REQUIRED_FILES"]], "transitnetwork (class in network_wrangler)": [[3, "network_wrangler.TransitNetwork"]], "__init__() (network_wrangler.transitnetwork method)": [[3, "network_wrangler.TransitNetwork.__init__"]], "apply() (network_wrangler.transitnetwork method)": [[3, "network_wrangler.TransitNetwork.apply"]], "apply_python_calculation() (network_wrangler.transitnetwork method)": [[3, "network_wrangler.TransitNetwork.apply_python_calculation"]], "apply_transit_feature_change() (network_wrangler.transitnetwork method)": [[3, "network_wrangler.TransitNetwork.apply_transit_feature_change"]], "apply_transit_managed_lane() (network_wrangler.transitnetwork method)": [[3, "network_wrangler.TransitNetwork.apply_transit_managed_lane"]], "check_network_connectivity() (network_wrangler.transitnetwork method)": [[3, "network_wrangler.TransitNetwork.check_network_connectivity"]], "config (network_wrangler.transitnetwork attribute)": [[3, "network_wrangler.TransitNetwork.config"]], "empty() (network_wrangler.transitnetwork static method)": [[3, "network_wrangler.TransitNetwork.empty"]], "feed (network_wrangler.transitnetwork attribute)": [[3, "network_wrangler.TransitNetwork.feed"]], "feed_path (network_wrangler.transitnetwork attribute)": [[3, "network_wrangler.TransitNetwork.feed_path"]], "graph (network_wrangler.transitnetwork attribute)": [[3, "network_wrangler.TransitNetwork.graph"]], "id_scalar (network_wrangler.transitnetwork attribute)": [[3, "network_wrangler.TransitNetwork.id_scalar"]], "read() (network_wrangler.transitnetwork static method)": [[3, "network_wrangler.TransitNetwork.read"]], "road_net (network_wrangler.transitnetwork attribute)": [[3, "network_wrangler.TransitNetwork.road_net"]], "route_between_nodes() (network_wrangler.transitnetwork static method)": [[3, "network_wrangler.TransitNetwork.route_between_nodes"]], "route_ids_in_routestxt() (network_wrangler.transitnetwork static method)": [[3, "network_wrangler.TransitNetwork.route_ids_in_routestxt"]], "select_transit_features() (network_wrangler.transitnetwork method)": [[3, "network_wrangler.TransitNetwork.select_transit_features"]], "select_transit_features_by_nodes() (network_wrangler.transitnetwork method)": [[3, "network_wrangler.TransitNetwork.select_transit_features_by_nodes"]], "set_roadnet() (network_wrangler.transitnetwork method)": [[3, "network_wrangler.TransitNetwork.set_roadnet"]], "shape_ids_in_shapestxt() (network_wrangler.transitnetwork static method)": [[3, "network_wrangler.TransitNetwork.shape_ids_in_shapestxt"]], "shapes_foreign_key (network_wrangler.transitnetwork attribute)": [[3, "network_wrangler.TransitNetwork.shapes_foreign_key"]], "stop_ids_in_stopstxt() (network_wrangler.transitnetwork static method)": [[3, "network_wrangler.TransitNetwork.stop_ids_in_stopstxt"]], "stops_foreign_key (network_wrangler.transitnetwork attribute)": [[3, "network_wrangler.TransitNetwork.stops_foreign_key"]], "transit_net_to_gdf() (network_wrangler.transitnetwork static method)": [[3, "network_wrangler.TransitNetwork.transit_net_to_gdf"]], "trip_ids_in_tripstxt() (network_wrangler.transitnetwork static method)": [[3, "network_wrangler.TransitNetwork.trip_ids_in_tripstxt"]], "validate_feed() (network_wrangler.transitnetwork static method)": [[3, "network_wrangler.TransitNetwork.validate_feed"]], "validate_frequencies() (network_wrangler.transitnetwork method)": [[3, "network_wrangler.TransitNetwork.validate_frequencies"]], "validate_network_keys() (network_wrangler.transitnetwork static method)": [[3, "network_wrangler.TransitNetwork.validate_network_keys"]], "validate_road_network_consistencies() (network_wrangler.transitnetwork method)": [[3, "network_wrangler.TransitNetwork.validate_road_network_consistencies"]], "validate_transit_shapes() (network_wrangler.transitnetwork method)": [[3, "network_wrangler.TransitNetwork.validate_transit_shapes"]], "validate_transit_stops() (network_wrangler.transitnetwork method)": [[3, "network_wrangler.TransitNetwork.validate_transit_stops"]], "validated_frequencies (network_wrangler.transitnetwork attribute)": [[3, "network_wrangler.TransitNetwork.validated_frequencies"]], "validated_road_network_consistency (network_wrangler.transitnetwork attribute)": [[3, "network_wrangler.TransitNetwork.validated_road_network_consistency"]], "write() (network_wrangler.transitnetwork method)": [[3, "network_wrangler.TransitNetwork.write"]], "module": [[4, "module-network_wrangler.logger"], [5, "module-network_wrangler.utils"], [6, "module-network_wrangler"]], "network_wrangler.logger": [[4, "module-network_wrangler.logger"]], "setuplogging() (in module network_wrangler.logger)": [[4, "network_wrangler.logger.setupLogging"]], "all (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.ALL"]], "area (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.AREA"]], "azimuth (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.AZIMUTH"]], "arcdirect() (network_wrangler.utils.geodesic method)": [[5, "network_wrangler.utils.Geodesic.ArcDirect"]], "arcdirectline() (network_wrangler.utils.geodesic method)": [[5, "network_wrangler.utils.Geodesic.ArcDirectLine"]], "cap_all (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.CAP_ALL"]], "cap_c1 (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.CAP_C1"]], "cap_c1p (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.CAP_C1p"]], "cap_c2 (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.CAP_C2"]], "cap_c3 (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.CAP_C3"]], "cap_c4 (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.CAP_C4"]], "cap_mask (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.CAP_MASK"]], "cap_none (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.CAP_NONE"]], "distance (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.DISTANCE"]], "distance_in (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.DISTANCE_IN"]], "direct() (network_wrangler.utils.geodesic method)": [[5, "network_wrangler.utils.Geodesic.Direct"]], "directline() (network_wrangler.utils.geodesic method)": [[5, "network_wrangler.utils.Geodesic.DirectLine"]], "empty (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.EMPTY"]], "geodesicscale (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.GEODESICSCALE"]], "geographiclib_geodesic_order (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.GEOGRAPHICLIB_GEODESIC_ORDER"]], "geodesic (class in network_wrangler.utils)": [[5, "network_wrangler.utils.Geodesic"]], "inverse() (network_wrangler.utils.geodesic method)": [[5, "network_wrangler.utils.Geodesic.Inverse"]], "inverseline() (network_wrangler.utils.geodesic method)": [[5, "network_wrangler.utils.Geodesic.InverseLine"]], "latitude (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.LATITUDE"]], "longitude (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.LONGITUDE"]], "long_unroll (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.LONG_UNROLL"]], "line() (network_wrangler.utils.geodesic method)": [[5, "network_wrangler.utils.Geodesic.Line"]], "linestring (class in network_wrangler.utils)": [[5, "network_wrangler.utils.LineString"]], "out_all (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.OUT_ALL"]], "out_mask (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.OUT_MASK"]], "polygon() (network_wrangler.utils.geodesic method)": [[5, "network_wrangler.utils.Geodesic.Polygon"]], "reducedlength (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.REDUCEDLENGTH"]], "standard (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.STANDARD"]], "wgs84 (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.WGS84"]], "a (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.a"]], "almost_equals() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.almost_equals"]], "area (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.area"]], "boundary (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.boundary"]], "bounds (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.bounds"]], "buffer() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.buffer"]], "centroid (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.centroid"]], "contains() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.contains"]], "contains_properly() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.contains_properly"]], "convex_hull (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.convex_hull"]], "coords (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.coords"]], "covered_by() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.covered_by"]], "covers() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.covers"]], "create_line_string() (in module network_wrangler.utils)": [[5, "network_wrangler.utils.create_line_string"]], "create_location_reference_from_nodes() (in module network_wrangler.utils)": [[5, "network_wrangler.utils.create_location_reference_from_nodes"]], "create_unique_shape_id() (in module network_wrangler.utils)": [[5, "network_wrangler.utils.create_unique_shape_id"]], "crosses() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.crosses"]], "difference() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.difference"]], "disjoint() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.disjoint"]], "distance() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.distance"]], "dwithin() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.dwithin"]], "envelope (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.envelope"]], "equals() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.equals"]], "equals_exact() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.equals_exact"]], "f (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.f"]], "geom_type (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.geom_type"]], "geometrytype() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.geometryType"]], "get_bearing() (in module network_wrangler.utils)": [[5, "network_wrangler.utils.get_bearing"]], "has_z (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.has_z"]], "hausdorff_distance() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.hausdorff_distance"]], "haversine_distance() (in module network_wrangler.utils)": [[5, "network_wrangler.utils.haversine_distance"]], "interpolate() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.interpolate"]], "intersection() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.intersection"]], "intersects() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.intersects"]], "is_closed (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.is_closed"]], "is_empty (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.is_empty"]], "is_ring (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.is_ring"]], "is_simple (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.is_simple"]], "is_valid (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.is_valid"]], "length (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.length"]], "line_interpolate_point() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.line_interpolate_point"]], "line_locate_point() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.line_locate_point"]], "link_df_to_json() (in module network_wrangler.utils)": [[5, "network_wrangler.utils.link_df_to_json"]], "make_slug() (in module network_wrangler.utils)": [[5, "network_wrangler.utils.make_slug"]], "maxit1_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.maxit1_"]], "maxit2_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.maxit2_"]], "minimum_clearance (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.minimum_clearance"]], "minimum_rotated_rectangle (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.minimum_rotated_rectangle"]], "na1_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.nA1_"]], "na2_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.nA2_"]], "na3_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.nA3_"]], "na3x_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.nA3x_"]], "nc1_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.nC1_"]], "nc1p_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.nC1p_"]], "nc2_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.nC2_"]], "nc3_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.nC3_"]], "nc3x_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.nC3x_"]], "nc4_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.nC4_"]], "nc4x_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.nC4x_"]], "network_wrangler.utils": [[5, "module-network_wrangler.utils"]], "normalize() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.normalize"]], "offset_curve() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.offset_curve"]], "offset_location_reference() (in module network_wrangler.utils)": [[5, "network_wrangler.utils.offset_location_reference"]], "offset_point_with_distance_and_bearing() (in module network_wrangler.utils)": [[5, "network_wrangler.utils.offset_point_with_distance_and_bearing"]], "oriented_envelope (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.oriented_envelope"]], "overlaps() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.overlaps"]], "parallel_offset() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.parallel_offset"]], "parse_time_spans() (in module network_wrangler.utils)": [[5, "network_wrangler.utils.parse_time_spans"]], "point_df_to_geojson() (in module network_wrangler.utils)": [[5, "network_wrangler.utils.point_df_to_geojson"]], "point_on_surface() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.point_on_surface"]], "project() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.project"]], "relate() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.relate"]], "relate_pattern() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.relate_pattern"]], "representative_point() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.representative_point"]], "reverse() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.reverse"]], "segmentize() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.segmentize"]], "simplify() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.simplify"]], "svg() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.svg"]], "symmetric_difference() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.symmetric_difference"]], "tiny_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.tiny_"]], "tol0_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.tol0_"]], "tol1_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.tol1_"]], "tol2_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.tol2_"]], "tolb_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.tolb_"]], "topological_sort() (in module network_wrangler.utils)": [[5, "network_wrangler.utils.topological_sort"]], "touches() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.touches"]], "type (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.type"]], "union() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.union"]], "update_df() (in module network_wrangler.utils)": [[5, "network_wrangler.utils.update_df"]], "within() (network_wrangler.utils.linestring method)": [[5, "network_wrangler.utils.LineString.within"]], "wkb (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.wkb"]], "wkb_hex (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.wkb_hex"]], "wkt (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.wkt"]], "xthresh_ (network_wrangler.utils.geodesic attribute)": [[5, "network_wrangler.utils.Geodesic.xthresh_"]], "xy (network_wrangler.utils.linestring property)": [[5, "network_wrangler.utils.LineString.xy"]], "network_wrangler": [[6, "module-network_wrangler"]]}}) \ No newline at end of file diff --git a/todo/index.html b/todo/index.html new file mode 100644 index 00000000..450fef09 --- /dev/null +++ b/todo/index.html @@ -0,0 +1,163 @@ + + + + + + + To Do List — Network Wrangler documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

To Do List

+
+

Todo

+

validate links and nodes dictionary

+
+

(The original entry is located in /home/runner/work/network_wrangler/network_wrangler/network_wrangler/roadwaynetwork.py:docstring of network_wrangler.roadwaynetwork.RoadwayNetwork.add_new_roadway_feature_change, line 11.)

+
+

Todo

+

decide on connectors info when they are more specific in project card

+
+

(The original entry is located in /home/runner/work/network_wrangler/network_wrangler/network_wrangler/roadwaynetwork.py:docstring of network_wrangler.roadwaynetwork.RoadwayNetwork.apply_managed_lane_feature_change, line 8.)

+
+

Todo

+

make this a more rigorous test

+
+

(The original entry is located in /home/runner/work/network_wrangler/network_wrangler/network_wrangler/roadwaynetwork.py:docstring of network_wrangler.roadwaynetwork.RoadwayNetwork.create_managed_lane_network, line 25.)

+
+

Todo

+

Right now we don’t filter the nodes because transit-only

+
+

(The original entry is located in /home/runner/work/network_wrangler/network_wrangler/network_wrangler/roadwaynetwork.py:docstring of network_wrangler.roadwaynetwork.RoadwayNetwork.get_modal_links_nodes, line 12.)

+
+

Todo

+

Consider caching graphs if they take a long time.

+
+

(The original entry is located in /home/runner/work/network_wrangler/network_wrangler/network_wrangler/roadwaynetwork.py:docstring of network_wrangler.roadwaynetwork.RoadwayNetwork.is_network_connected, line 11.)

+
+

Todo

+

Turn off fast=True as default

+
+

(The original entry is located in /home/runner/work/network_wrangler/network_wrangler/network_wrangler/roadwaynetwork.py:docstring of network_wrangler.roadwaynetwork.RoadwayNetwork.read, line 25.)

+
+

Todo

+

Make this much more sophisticated, for example attach link info to shapes

+
+

(The original entry is located in /home/runner/work/network_wrangler/network_wrangler/network_wrangler/roadwaynetwork.py:docstring of network_wrangler.roadwaynetwork.RoadwayNetwork.roadway_net_to_gdf, line 8.)

+
+

Todo

+

Make graph a reference to associated RoadwayNetwork’s graph, not its own thing.

+
+

(The original entry is located in /home/runner/work/network_wrangler/network_wrangler/network_wrangler/transitnetwork.py:docstring of network_wrangler.transitnetwork.TransitNetwork.__init__, line 3.)

+
+

Todo

+

fill out this method

+
+

(The original entry is located in /home/runner/work/network_wrangler/network_wrangler/network_wrangler/transitnetwork.py:docstring of network_wrangler.transitnetwork.TransitNetwork.empty, line 5.)

+
+

Todo

+

Make more sophisticated.

+
+

(The original entry is located in /home/runner/work/network_wrangler/network_wrangler/network_wrangler/transitnetwork.py:docstring of network_wrangler.transitnetwork.TransitNetwork.transit_net_to_gdf, line 5.)

+
+ + +
+
+
+ +
+ +
+

© Copyright 2020-2022, Metropolitan Council, Metropolitan Transportation Commission.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file