From f32e6f9638c1657ed9d41bbe02dabe499ba90e43 Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Mon, 21 Oct 2024 17:30:18 -0600 Subject: [PATCH 01/44] checking in current branch --- docker-compose-connect.yml | 9 +++- docker-compose-kafka.yml | 7 ++- kafka-connect/Dockerfile | 2 +- kafka/Dockerfile.jikkou | 9 ++++ kafka/application.conf | 2 +- kafka/kafka-connectors-template.jinja | 75 +++++++++++++++++++++++++++ kafka/kafka-connectors-values.yaml | 70 +++++++++++++++++++++++++ kafka/kafka_init.sh | 16 +++++- sample.env | 3 -- 9 files changed, 183 insertions(+), 10 deletions(-) create mode 100644 kafka/kafka-connectors-template.jinja create mode 100644 kafka/kafka-connectors-values.yaml diff --git a/docker-compose-connect.yml b/docker-compose-connect.yml index c80d7be..2579d3d 100644 --- a/docker-compose-connect.yml +++ b/docker-compose-connect.yml @@ -16,6 +16,11 @@ services: memory: 4G ports: - "8083:8083" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8083/connectors"] + interval: 30s + timeout: 10s + retries: 4 depends_on: mongo: condition: service_healthy @@ -43,5 +48,5 @@ services: CONNECT_LOG4J_ROOT_LOGLEVEL: ${CONNECT_LOG_LEVEL} CONNECT_LOG4J_LOGGERS: "org.apache.kafka.connect.runtime.rest=${CONNECT_LOG_LEVEL},org.reflections=${CONNECT_LOG_LEVEL},com.mongodb.kafka=${CONNECT_LOG_LEVEL}" CONNECT_PLUGIN_PATH: /usr/share/confluent-hub-components - volumes: - - ${CONNECT_SCRIPT_RELATIVE_PATH}:/scripts/connect_start.sh \ No newline at end of file + # volumes: + # - ${CONNECT_SCRIPT_RELATIVE_PATH}:/scripts/connect_start.sh \ No newline at end of file diff --git a/docker-compose-kafka.yml b/docker-compose-kafka.yml index 73aab7c..1a01a95 100644 --- a/docker-compose-kafka.yml +++ b/docker-compose-kafka.yml @@ -52,7 +52,7 @@ services: build: context: kafka dockerfile: Dockerfile.jikkou - restart: on-failure:3 # try up to 3 times + restart: on-failure:3 deploy: resources: limits: @@ -71,7 +71,10 @@ services: KAFKA_TOPIC_CREATE_GEOJSONCONVERTER: ${KAFKA_TOPIC_CREATE_GEOJSONCONVERTER} KAFKA_TOPIC_CREATE_CONFLICTMONITOR: ${KAFKA_TOPIC_CREATE_CONFLICTMONITOR} KAFKA_TOPIC_CREATE_DEDUPLICATOR: ${KAFKA_TOPIC_CREATE_DEDUPLICATOR} - + MONGO_CONNECTOR_USERNAME: ${MONGO_ADMIN_DB_USER} + MONGO_CONNECTOR_PASSWORD: ${MONGO_ADMIN_DB_PASS} + MONGO_DB_IP: ${MONGO_IP} + MONGO_DB_NAME: ${MONGO_DB_NAME} kafka-schema-registry: profiles: diff --git a/kafka-connect/Dockerfile b/kafka-connect/Dockerfile index 0be3886..783817a 100644 --- a/kafka-connect/Dockerfile +++ b/kafka-connect/Dockerfile @@ -7,4 +7,4 @@ RUN confluent-hub install --no-prompt mongodb/kafka-connect-mongodb:1.13.0 # Docs: https://docs.confluent.io/platform/current/connect/transforms/overview.html RUN confluent-hub install --no-prompt confluentinc/connect-transforms:1.4.7 -CMD ["bash", "-c", "/scripts/connect_wait.sh"] \ No newline at end of file +# CMD ["bash", "-c", "/scripts/connect_wait.sh"] \ No newline at end of file diff --git a/kafka/Dockerfile.jikkou b/kafka/Dockerfile.jikkou index 139b4a1..c76b06e 100644 --- a/kafka/Dockerfile.jikkou +++ b/kafka/Dockerfile.jikkou @@ -3,12 +3,21 @@ FROM streamthoughts/jikkou:0.35.2 # Root user is required to run the 'jikkou apply' command in the kafka_init.sh script USER root +# install yq for kafka connect status check +RUN apk add yq + COPY ./application.conf /app/application.conf COPY ./jikkouconfig /etc/jikkou/config + +# kafka topic creation files COPY ./kafka-topics-template.jinja /app/kafka-topics-template.jinja COPY ./kafka-topics-values.yaml /app/kafka-topics-values.yaml COPY ./kafka_init.sh /app/kafka_init.sh +# kafka connect creation files +COPY ./kafka-connectors-template.jinja /app/kafka-connectors-template.jinja +COPY ./kafka-connectors-values.yaml /app/kafka-connectors-values.yaml + # Create/update topics then exit container ENTRYPOINT ./kafka_init.sh diff --git a/kafka/application.conf b/kafka/application.conf index 9956fce..c8e61fe 100644 --- a/kafka/application.conf +++ b/kafka/application.conf @@ -62,7 +62,7 @@ jikkou { # Use when 'authMethod' is 'basicauth' to specify the password for Authorization Basic header basicAuthPassword = null # Enable debug logging - debugLoggingEnabled = false + debugLoggingEnabled = true # # Ssl Config: Use when 'authMethod' is 'ssl' # # The location of the key store file. diff --git a/kafka/kafka-connectors-template.jinja b/kafka/kafka-connectors-template.jinja new file mode 100644 index 0000000..be4259c --- /dev/null +++ b/kafka/kafka-connectors-template.jinja @@ -0,0 +1,75 @@ +{# ----------------- Create Topics for app ----------------- #} +{% macro create_connector(app) %} + +{# Table Topics #} +{% for connector in app.connectors %} +--- +apiVersion: "kafka.jikkou.io/v1beta1" {# The api version (required) #} +kind: "KafkaConnector" {# The resource kind (required) #} +metadata: + name: sink.{{ connector.collectionName }} + labels: + kafka.jikkou.io/connect-cluster: {{ values.clusterName }} +spec: + connectorClass: {{ system.env.KAFKA_CONNECT_CONNECTOR_CLASS | default(values.connectorClass) }} + tasksMax: {{ system.env.KAFKA_TOPIC_PARTITIONS | default(values.tasksMax) }} + config: + collection: {{ connector.collectionName }} + connection.uri: mongodb://{{ system.env.MONGO_CONNECTOR_USERNAME }}:{{ system.env.MONGO_CONNECTOR_PASSWORD }}@{{ system.env.MONGO_DB_IP }}:27017/ + connector.class: com.mongodb.kafka.connect.MongoSinkConnector + database: {{ system.env.MONGO_DB_NAME }} + errors.log.enable: false + errors.log.include.messages: false + errors.tolerance: all + group.id: connector-consumer + key.converter: org.apache.kafka.connect.storage.StringConverter + key.converter.schemas.enable: false + mongo.errors.tolerance: all + topics: {{ connector.topicName }} + value.converter: org.apache.kafka.connect.json.JsonConverter + value.converter.schemas.enable: false + + {%+ if connector.generateTimestamp %}errors.deadletterqueue.topic.name: {{ connector.topicName }}.dlq{% endif %} + {%+ if connector.generateTimestamp %}errors.deadletterqueue.topic.replication.factor: {{ system.env.KAFKA_TOPIC_REPLICAS | default(values.replicas) }}{% endif %} + + {%+ if connector.generateTimestamp %}transforms: AddTimestamp,AddedTimestampConverter{% endif %} + {%+ if connector.generateTimestamp %}transforms.AddedTimestampConverter.field: recordGeneratedAt{% endif %} + {%+ if connector.generateTimestamp %}transforms.AddedTimestampConverter.target.type: Timestamp{% endif %} + {%+ if connector.generateTimestamp %}transforms.AddedTimestampConverter.type: org.apache.kafka.connect.transforms.TimestampConverter$Value{% endif %} + {%+ if connector.generateTimestamp %}transforms.AddTimestamp.timestamp.field: recordGeneratedAt{% endif %} + {%+ if connector.generateTimestamp %}transforms.AddTimestamp.type: org.apache.kafka.connect.transforms.InsertField$Value{% endif %} + + {%+ if connector.useTimestamp %}transforms: TimestampConverter{% endif %} + {%+ if connector.useTimestamp %}transforms.TimestampConverter.field: {{ connector.timestampField }}{% endif %} + {%+ if connector.useTimestamp %}transforms.TimestampConverter.type: org.apache.kafka.connect.transforms.TimestampConverter$Value{% endif %} + {%+ if connector.useTimestamp %}transforms.TimestampConverter.target.type: Timestamp{% endif %} + + {%+ if connector.useKey %}document.id.strategy: com.mongodb.kafka.connect.sink.processor.id.strategy.PartialValueStrategy{% endif %} + {%+ if connector.useKey %}document.id.strategy.partial.value.projection.list: {{ connector.keyField }}{% endif %} + {%+ if connector.useKey %}document.id.strategy.partial.value.projection.type: AllowList{% endif %} + {%+ if connector.useKey %}document.id.strategy.overwrite.existing: true{% endif %} + + state: "RUNNING" +{% endfor %} + +{% endmacro %} + +{#------- Create topics for apps with env variable = true ----------#} +{% if system.env.KAFKA_TOPIC_CREATE_ODE %} +{{ create_connector(values.apps.ode) }} +{% endif %} + +{# {% if system.env.KAFKA_TOPIC_CREATE_GEOJSONCONVERTER %} +{{ create_topics(values.apps.geojsonconverter) }} +{% endif %} + +{% if system.env.KAFKA_TOPIC_CREATE_CONFLICTMONITOR %} +{{ create_topics(values.apps.conflictmonitor) }} +{% endif %} + +{% if system.env.KAFKA_TOPIC_CREATE_DEDUPLICATOR %} +{{ create_topics(values.apps.deduplicator) }} #} +{# {% endif %} #} + + + diff --git a/kafka/kafka-connectors-values.yaml b/kafka/kafka-connectors-values.yaml new file mode 100644 index 0000000..226cebe --- /dev/null +++ b/kafka/kafka-connectors-values.yaml @@ -0,0 +1,70 @@ +#====================================================================================== +# Topic names and configuration settings +#====================================================================================== + +#-------------------------------------------------------------------------------------- +# Default values of topic configuration settings, +# can be overridden by environment variables. +# +# Ref: https://kafka.apache.org/documentation/#topicconfigs +#-------------------------------------------------------------------------------------- + +# env var: KAFKA_TOPIC_PARTITIONS +clusterName: "kafka-connect" +connectorClass: "com.mongodb.kafka.connect.MongoSinkConnector" +tasksMax: 1 + + +#-------------------------------------------------------------------------------------- +# Topics are grouped by application. Apps with the corresponding environment variable +# equal to true are create or updated. +# - ode +# - geojsonconverter +# - conflictmonitor +# - deduplicator +# +# The topics for each app are grouped into "Stream" topics and "Table" topics: +# - Stream Topics are normal topics with cleanup.policy = delete +# - Table Topics are intended to back KTables and have cleanup.policy = compact +# +#-------------------------------------------------------------------------------------- +apps: + ode: + name: jpo-ode + connectors: + - + topicName: topic.FilteredOdeBsmJson + collectionName: OdeBsmJson + generateTimestamp: true + - + topicName: topic.OdeMapJson + collectionName: OdeMapJson + generateTimestamp: true + - + topicName: topic.OdeSpatJson + collectionName: OdeSpatJson + generateTimestamp: true + - + topicName: topic.OdeTimJson + collectionName: OdeTimJson + generateTimestamp: true + - + topicName: topic.OdeTimJsonTMCFiltered + collectionName: OdeTimJsonTMCFiltered + generateTimestamp: true + - + topicName: topic.OdePsmJson + collectionName: OdePsmJson + generateTimestamp: true + - + topicName: topic.OdeDriverAlertJson + collectionName: OdeDriverAlertJson + generateTimestamp: true + - + topicName: topic.OdeSrmJson + collectionName: OdeSrmJson + generateTimestamp: true + - + topicName: topic.OdeSsmJson + collectionName: OdeSsmJson + generateTimestamp: true \ No newline at end of file diff --git a/kafka/kafka_init.sh b/kafka/kafka_init.sh index aa4669d..8e0c2e1 100644 --- a/kafka/kafka_init.sh +++ b/kafka/kafka_init.sh @@ -15,5 +15,19 @@ echo "KAFKA_TOPIC_CREATE_DEDUPLICATOR=$KAFKA_TOPIC_CREATE_DEDUPLICATOR" --files kafka-topics-template.jinja \ --values-files kafka-topics-values.yaml +# sleep +sleep 10 - +if ./jikkou health get kafkaconnect | yq -e '.status.name == "UP"' > /dev/null; + echo "Kafka Connect is ready" + ./jikkou health get kafkaconnect + + # Run the validate and apply scripts + ./jikkou validate \ + --files kafka-connectors-template.jinja \ + --values-files kafka-connectors-values.yaml + + ./jikkou apply \ + --files kafka-connectors-template.jinja \ + --values-files kafka-connectors-values.yaml +fi \ No newline at end of file diff --git a/sample.env b/sample.env index 3f471bd..30f4938 100644 --- a/sample.env +++ b/sample.env @@ -70,7 +70,4 @@ MONGO_CREATE_INDEXES_SCRIPT_RELATIVE_PATH="./mongo/create_indexes.js" # NOTE: Required variables: [MONGODB, KAFKA] # Kafka connect log level CONNECT_LOG_LEVEL=ERROR -# Relative path to the Kafka init script, upper level directories are supported -# NOTE: This script is used to create kafka connectors -CONNECT_SCRIPT_RELATIVE_PATH="./kafka-connect/connect_start.sh" ### Kafka connect variables - END ### \ No newline at end of file From d2dd672e19075ef47a52d181326ee680e41b0aef Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:50:09 -0600 Subject: [PATCH 02/44] updates from testing kafka connectors --- docker-compose-kafka.yml | 5 +- kafka/Dockerfile.jikkou | 2 - kafka/kafka-connectors-template.jinja | 23 +-- kafka/kafka-connectors-values.yaml | 272 ++++++++++++++++++++++---- kafka/kafka_init.sh | 42 ++-- 5 files changed, 279 insertions(+), 65 deletions(-) diff --git a/docker-compose-kafka.yml b/docker-compose-kafka.yml index 1a01a95..1cf8792 100644 --- a/docker-compose-kafka.yml +++ b/docker-compose-kafka.yml @@ -52,7 +52,7 @@ services: build: context: kafka dockerfile: Dockerfile.jikkou - restart: on-failure:3 + restart: on-failure deploy: resources: limits: @@ -75,6 +75,9 @@ services: MONGO_CONNECTOR_PASSWORD: ${MONGO_ADMIN_DB_PASS} MONGO_DB_IP: ${MONGO_IP} MONGO_DB_NAME: ${MONGO_DB_NAME} + volumes: + - ./kafka/kafka-connectors-values.yaml:/app/kafka-connectors-values.yaml + - ./kafka/kafka-topics-values.yaml:/app/kafka-topics-values.yaml kafka-schema-registry: profiles: diff --git a/kafka/Dockerfile.jikkou b/kafka/Dockerfile.jikkou index c76b06e..9e9fb59 100644 --- a/kafka/Dockerfile.jikkou +++ b/kafka/Dockerfile.jikkou @@ -11,12 +11,10 @@ COPY ./jikkouconfig /etc/jikkou/config # kafka topic creation files COPY ./kafka-topics-template.jinja /app/kafka-topics-template.jinja -COPY ./kafka-topics-values.yaml /app/kafka-topics-values.yaml COPY ./kafka_init.sh /app/kafka_init.sh # kafka connect creation files COPY ./kafka-connectors-template.jinja /app/kafka-connectors-template.jinja -COPY ./kafka-connectors-values.yaml /app/kafka-connectors-values.yaml # Create/update topics then exit container ENTRYPOINT ./kafka_init.sh diff --git a/kafka/kafka-connectors-template.jinja b/kafka/kafka-connectors-template.jinja index be4259c..08bd183 100644 --- a/kafka/kafka-connectors-template.jinja +++ b/kafka/kafka-connectors-template.jinja @@ -1,4 +1,4 @@ -{# ----------------- Create Topics for app ----------------- #} +{# ----------------- Create Kafka Connectors to MongoDB per app ----------------- #} {% macro create_connector(app) %} {# Table Topics #} @@ -7,7 +7,7 @@ apiVersion: "kafka.jikkou.io/v1beta1" {# The api version (required) #} kind: "KafkaConnector" {# The resource kind (required) #} metadata: - name: sink.{{ connector.collectionName }} + name: sink.{{ connector.connectorName | default(connector.collectionName) }} labels: kafka.jikkou.io/connect-cluster: {{ values.clusterName }} spec: @@ -29,9 +29,6 @@ spec: value.converter: org.apache.kafka.connect.json.JsonConverter value.converter.schemas.enable: false - {%+ if connector.generateTimestamp %}errors.deadletterqueue.topic.name: {{ connector.topicName }}.dlq{% endif %} - {%+ if connector.generateTimestamp %}errors.deadletterqueue.topic.replication.factor: {{ system.env.KAFKA_TOPIC_REPLICAS | default(values.replicas) }}{% endif %} - {%+ if connector.generateTimestamp %}transforms: AddTimestamp,AddedTimestampConverter{% endif %} {%+ if connector.generateTimestamp %}transforms.AddedTimestampConverter.field: recordGeneratedAt{% endif %} {%+ if connector.generateTimestamp %}transforms.AddedTimestampConverter.target.type: Timestamp{% endif %} @@ -59,17 +56,17 @@ spec: {{ create_connector(values.apps.ode) }} {% endif %} -{# {% if system.env.KAFKA_TOPIC_CREATE_GEOJSONCONVERTER %} -{{ create_topics(values.apps.geojsonconverter) }} +{% if system.env.KAFKA_TOPIC_CREATE_GEOJSONCONVERTER %} +{{ create_connector(values.apps.geojsonconverter) }} {% endif %} {% if system.env.KAFKA_TOPIC_CREATE_CONFLICTMONITOR %} -{{ create_topics(values.apps.conflictmonitor) }} +{{ create_connector(values.apps.conflictmonitor) }} {% endif %} {% if system.env.KAFKA_TOPIC_CREATE_DEDUPLICATOR %} -{{ create_topics(values.apps.deduplicator) }} #} -{# {% endif %} #} - - - +{{ create_connector(values.apps.deduplicator) }} +{% else %} +{{ create_connector(values.apps.ode_duplicated) }} +{{ create_connector(values.apps.geojsonconverter_duplicated) }} +{% endif %} \ No newline at end of file diff --git a/kafka/kafka-connectors-values.yaml b/kafka/kafka-connectors-values.yaml index 226cebe..d22afdb 100644 --- a/kafka/kafka-connectors-values.yaml +++ b/kafka/kafka-connectors-values.yaml @@ -1,70 +1,276 @@ #====================================================================================== -# Topic names and configuration settings +# Kafka Connectors configuration settings #====================================================================================== #-------------------------------------------------------------------------------------- -# Default values of topic configuration settings, -# can be overridden by environment variables. +# Configuration parameters used to create kafka connectors to sync data from Kafka topics +# to MongoDB collections. # -# Ref: https://kafka.apache.org/documentation/#topicconfigs +# Ref: https://docs.confluent.io/platform/current/connect/index.html#connect-connectors #-------------------------------------------------------------------------------------- -# env var: KAFKA_TOPIC_PARTITIONS clusterName: "kafka-connect" connectorClass: "com.mongodb.kafka.connect.MongoSinkConnector" tasksMax: 1 #-------------------------------------------------------------------------------------- -# Topics are grouped by application. Apps with the corresponding environment variable +# Kafka Connectors are grouped by application. Apps with the corresponding environment variable # equal to true are create or updated. # - ode # - geojsonconverter # - conflictmonitor -# - deduplicator +# +# Some Kafka topics have a non-duplicated topic and a duplicated topic. The duplicated topic will +# used if the deduplicator is not enabled. +# IF env var: KAFKA_TOPIC_CREATE_DEDUPLICATOR = true +# - deduplicator +# ELSE +# - ode_duplicated +# - geojsonconverter_duplicated # -# The topics for each app are grouped into "Stream" topics and "Table" topics: -# - Stream Topics are normal topics with cleanup.policy = delete -# - Table Topics are intended to back KTables and have cleanup.policy = compact +# The Kafka Connectors have the following configuration settings: +# Required settings: +# - topicName: The name of the Kafka topic to read from +# - collectionName: The name of the MongoDB collection to write to +# Optional settings: +# - generateTimestamp: If true, the connector will add a timestamp field to the document +# - connectorName: The name of the connector +# - useTimestamp: converts the "timestampField" field at the top level of the value to a BSON date +# - timestampField: The name of the timestamp field +# - useKey: If true, the connector will use the "keyField" as the document _id in MongoDB +# - keyField: The name of the key field # #-------------------------------------------------------------------------------------- apps: ode: name: jpo-ode connectors: - - - topicName: topic.FilteredOdeBsmJson - collectionName: OdeBsmJson + - topicName: topic.OdeRawEncodedBSMJson + collectionName: OdeRawEncodedBSMJson generateTimestamp: true - - - topicName: topic.OdeMapJson - collectionName: OdeMapJson + - topicName: topic.OdeRawEncodedMAPJson + collectionName: OdeRawEncodedMAPJson generateTimestamp: true - - - topicName: topic.OdeSpatJson + - topicName: topic.OdeRawEncodedSPATJson + collectionName: OdeRawEncodedSPATJson + generateTimestamp: true + - topicName: topic.OdeSpatJson collectionName: OdeSpatJson generateTimestamp: true - - - topicName: topic.OdeTimJson - collectionName: OdeTimJson + - topicName: topic.OdeRawEncodedTIMJson + collectionName: OdeRawEncodedTIMJson generateTimestamp: true - - - topicName: topic.OdeTimJsonTMCFiltered + - topicName: topic.OdeTimJsonTMCFiltered collectionName: OdeTimJsonTMCFiltered generateTimestamp: true - - - topicName: topic.OdePsmJson + - topicName: topic.OdeTimBroadcastJson + collectionName: OdeTimBroadcastJson + generateTimestamp: true + - topicName: topic.OdeTIMCertExpirationTimeJson + collectionName: OdeTIMCertExpirationTimeJson + generateTimestamp: true + - topicName: topic.OdeRawEncodedPSMJson + collectionName: OdeRawEncodedPSMJson + generateTimestamp: true + - topicName: topic.OdePsmJson collectionName: OdePsmJson generateTimestamp: true - - - topicName: topic.OdeDriverAlertJson - collectionName: OdeDriverAlertJson + - topicName: topic.OdeRawEncodedSRMJson + collectionName: OdeRawEncodedSRMJson generateTimestamp: true - - - topicName: topic.OdeSrmJson + - topicName: topic.OdeSrmJson collectionName: OdeSrmJson generateTimestamp: true - - - topicName: topic.OdeSsmJson + - topicName: topic.OdeRawEncodedSSMJson + collectionName: OdeRawEncodedSSMJson + generateTimestamp: true + - topicName: topic.OdeSsmJson collectionName: OdeSsmJson - generateTimestamp: true \ No newline at end of file + generateTimestamp: true + - topicName: topic.OdeDriverAlertJson + collectionName: OdeDriverAlertJson + generateTimestamp: true + ode_duplicated: + name: ode-duplicated + connectors: + - topicName: topic.OdeMapJson + collectionName: OdeMapJson + generateTimestamp: true + - topicName: topic.OdeTimJson + collectionName: OdeTimJson + generateTimestamp: true + - topicName: topic.OdeBsmJson + collectionName: OdeBsmJson + generateTimestamp: true + geojsonconverter: + name: geojsonconverter + connectors: + - topicName: topic.ProcessedSpat + collectionName: ProcessedSpat + generateTimestamp: true + - topicName: topic.ProcessedBsm + collectionName: ProcessedBsm + generateTimestamp: true + geojsonconverter_duplicated: + name: geojsonconverter-duplicated + connectors: + - topicName: topic.ProcessedMap + collectionName: ProcessedMap + generateTimestamp: true + deduplicator: + name: deduplicator + connectors: + - topicName: topic.DeduplicatedProcessedMap + collectionName: ProcessedMap + generateTimestamp: true + connectorName: DeduplicatedProcessedMap + - topicName: topic.DeduplicatedOdeMapJson + collectionName: OdeMapJson + generateTimestamp: true + connectorName: DeduplicatedOdeMapJson + - topicName: topic.DeduplicatedOdeTimJson + collectionName: OdeTimJson + generateTimestamp: true + connectorName: DeduplicatedOdeTimJson + - topicName: topic.DeduplicatedOdeBsmJson + collectionName: OdeBsmJson + generateTimestamp: true + connectorName: DeduplicatedOdeBsmJson + conflictmonitor: + name: conflictmonitor + connectors: + # Record Events + - topicName: topic.CmStopLinePassageEvent + collectionName: CmStopLinePassageEvent + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmStopLineStopEvent + collectionName: CmStopLineStopEvent + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmSignalStateConflictEvents + collectionName: CmSignalStateConflictEvents + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmIntersectionReferenceAlignmentEvents + collectionName: CmIntersectionReferenceAlignmentEvents + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmSignalGroupAlignmentEvents + collectionName: CmSignalGroupAlignmentEvents + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmConnectionOfTravelEvent + collectionName: CmConnectionOfTravelEvent + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmLaneDirectionOfTravelEvent + collectionName: CmLaneDirectionOfTravelEvent + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmSpatTimeChangeDetailsEvent + collectionName: CmSpatTimeChangeDetailsEvent + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmSpatMinimumDataEvents + collectionName: CmSpatMinimumDataEvents + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmMapBroadcastRateEvents + collectionName: CmMapBroadcastRateEvents + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmMapMinimumDataEvents + collectionName: CmMapMinimumDataEvents + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmSpatBroadcastRateEvents + collectionName: CmSpatBroadcastRateEvents + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmTimestampDeltaEvent + collectionName: CmTimestampDeltaEvent + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmEventStateProgressionEvent + collectionName: CmEventStateProgressionEvent + generateTimestamp: true + timestampField: eventGeneratedAt + + # Record BSM events: + - topicName: topic.CmBsmEvents + collectionName: CmBsmEvents + generateTimestamp: true + + # Record Assessments: + - topicName: topic.CmLaneDirectionOfTravelAssessment + collectionName: CmLaneDirectionOfTravelAssessment + generateTimestamp: true + timestampField: assessmentGeneratedAt + - topicName: topic.CmConnectionOfTravelAssessment + collectionName: CmConnectionOfTravelAssessment + generateTimestamp: true + timestampField: assessmentGeneratedAt + - topicName: topic.CmSignalStateEventAssessment + collectionName: CmSignalStateEventAssessment + generateTimestamp: true + timestampField: assessmentGeneratedAt + - topicName: topic.CmStopLineStopAssessment + collectionName: CmStopLineStopAssessment + generateTimestamp: true + timestampField: assessmentGeneratedAt + + # Record Notifications + - topicName: topic.CmSpatTimeChangeDetailsNotification + collectionName: CmSpatTimeChangeDetailsNotification + generateTimestamp: true + timestampField: notificationGeneratedAt + - topicName: topic.CmLaneDirectionOfTravelNotification + collectionName: CmLaneDirectionOfTravelNotification + generateTimestamp: true + timestampField: notificationGeneratedAt + - topicName: topic.CmConnectionOfTravelNotification + collectionName: CmConnectionOfTravelNotification + generateTimestamp: true + timestampField: notificationGeneratedAt + - topicName: topic.CmAppHealthNotifications + collectionName: CmAppHealthNotifications + generateTimestamp: true + timestampField: notificationGeneratedAt + - topicName: topic.CmSignalStateConflictNotification + collectionName: CmSignalStateConflictNotification + generateTimestamp: true + timestampField: notificationGeneratedAt + - topicName: topic.CmSignalGroupAlignmentNotification + collectionName: CmSignalGroupAlignmentNotification + generateTimestamp: true + timestampField: notificationGeneratedAt + - topicName: topic.CmNotification + collectionName: CmNotification + generateTimestamp: true + timestampField: notificationGeneratedAt + useKey: true + keyField: key + - topicName: topic.CmStopLineStopNotification + collectionName: CmStopLineStopNotification + generateTimestamp: true + timestampField: notificationGeneratedAt + useKey: true + keyField: key + - topicName: topic.CmStopLinePassageNotification + collectionName: CmStopLinePassageNotification + generateTimestamp: true + timestampField: notificationGeneratedAt + useKey: true + keyField: key + - topicName: topic.CmTimestampDeltaNotification + collectionName: CmTimestampDeltaNotification + generateTimestamp: true + timestampField: notificationGeneratedAt + useKey: true + keyField: key + - topicName: topic.CmEventStateProgressionNotification + collectionName: CmEventStateProgressionNotification + generateTimestamp: true + timestampField: eventGeneratedAt \ No newline at end of file diff --git a/kafka/kafka_init.sh b/kafka/kafka_init.sh index 8e0c2e1..d2fd269 100644 --- a/kafka/kafka_init.sh +++ b/kafka/kafka_init.sh @@ -15,19 +15,29 @@ echo "KAFKA_TOPIC_CREATE_DEDUPLICATOR=$KAFKA_TOPIC_CREATE_DEDUPLICATOR" --files kafka-topics-template.jinja \ --values-files kafka-topics-values.yaml -# sleep -sleep 10 - -if ./jikkou health get kafkaconnect | yq -e '.status.name == "UP"' > /dev/null; - echo "Kafka Connect is ready" - ./jikkou health get kafkaconnect - - # Run the validate and apply scripts - ./jikkou validate \ - --files kafka-connectors-template.jinja \ - --values-files kafka-connectors-values.yaml - - ./jikkou apply \ - --files kafka-connectors-template.jinja \ - --values-files kafka-connectors-values.yaml -fi \ No newline at end of file +# Set the maximum number of retries +MAX_RETRIES=5 +RETRY_COUNT=0 + +# Retry the health check until it is ready or the retry limit is reached +# This assumes that if the retry limit is reached that kafka connect is not deployed +until ./jikkou health get kafkaconnect | yq -e '.status.name == "UP"' > /dev/null; do + if [ "$RETRY_COUNT" -ge "$MAX_RETRIES" ]; then + echo "Assuming Kafka Connect is not deployed, exiting." + exit 1 + fi + echo "Waiting 10 sec for Kafka Connect to be ready (Attempt: $((RETRY_COUNT+1))/$MAX_RETRIES)" + RETRY_COUNT=$((RETRY_COUNT+1)) + sleep 10 +done + +./jikkou health get kafkaconnect + +# Run the validate and apply scripts +./jikkou validate \ + --files kafka-connectors-template.jinja \ + --values-files kafka-connectors-values.yaml + +./jikkou apply \ + --files kafka-connectors-template.jinja \ + --values-files kafka-connectors-values.yaml \ No newline at end of file From d8d82d188e904184d7e1604770f2ab59f167a133 Mon Sep 17 00:00:00 2001 From: john-wiens Date: Tue, 29 Oct 2024 14:26:52 -0600 Subject: [PATCH 03/44] Removing Conflict Monitor Unique Components from index script --- mongo/create_indexes.js | 420 +++++++++++++++++++++++++++++----------- mongo/manage_volume.js | 246 +++++++++++++++++++++++ sample.env | 10 + 3 files changed, 560 insertions(+), 116 deletions(-) create mode 100644 mongo/manage_volume.js diff --git a/mongo/create_indexes.js b/mongo/create_indexes.js index 4457a5b..39ef00d 100644 --- a/mongo/create_indexes.js +++ b/mongo/create_indexes.js @@ -1,115 +1,151 @@ // Create indexes on all collections /* -This script is responsible for initializing the replica set, creating collections, adding indexes and TTLs +This is the second script responsible for configuring mongoDB automatically on startup. +This script is responsible for creating users, creating collections, adding indexes, and configuring TTLs +For more information see the header in a_init_replicas.js */ + +console.log(""); console.log("Running create_indexes.js"); -const ode_db = process.env.MONGO_DB_NAME; -const rw_user = process.env.MONGO_READ_WRITE_USER; -const rw_pass = process.env.MONGO_READ_WRITE_PASS; -const ttlInDays = process.env.MONGO_COLLECTION_TTL; // TTL in days -const expire_seconds = ttlInDays * 24 * 60 * 60; -const retry_milliseconds = 5000; +// Setup Username and Password Definitions +const MONGO_ROOT_USERNAME = process.env.MONGO_ROOT_USERNAME; +const MONGO_ROOT_PASSWORD = process.env.MONGO_ROOT_PASSWORD; -console.log("DB Name: " + ode_db); +const MONGO_READ_WRITE_USER=process.MONGO_READ_WRITE_USER; +const MONGO_READ_WRITE_PASS=process.MONGO_READ_WRITE_PASS; -try { - console.log("Initializing replica set..."); +const MONGO_READ_USERNAME = process.env.MONGO_READ_USER; +const MONGO_READ_PASSWORD = process.env.MONGO_READ_PASS; + +// Prometheus Exporter User +const MONGO_EXPORTER_USERNAME = process.env.MONGO_EXPORTER_USERNAME; +const MONGO_EXPORTER_PASSWORD = process.env.MONGO_EXPORTER_PASSWORD; + +const DATABASE_NAME = process.env.MONGO_DATABASE_NAME || "CV"; + +const expireSeconds = process.env.MONGO_DATA_RETENTION_SECONDS || 5184000; // 2 months +const ttlExpireSeconds = process.env.MONGO_ASN_RETENTION_SECONDS || 86400; // 24 hours +const retryMilliseconds = 10000; + + +const users = [ + // {username: CM_MONGO_ROOT_USERNAME, password: CM_MONGO_ROOT_PASSWORD, roles: "root", database: "admin" }, + {username: MONGO_READ_WRITE_USER, password: MONGO_READ_WRITE_USER, permissions: [{role: "readWrite", database: DATABASE_NAME}]}, + {username: MONGO_USER_USERNAME, password: MONGO_USER_PASSWORD, permissions: [{role: "read", database: DATABASE_NAME}]}, + {username: MONGO_EXPORTER_USERNAME, password: MONGO_EXPORTER_PASSWORD, permissions: [{role: "clusterMonitor", database: "admin"}, {role: "read", database: DATABASE_NAME}]} +]; - var config = { - "_id": "rs0", - "version": 1, - "members": [ - { - "_id": 0, - "host": "mongo:27017", - "priority": 2 - }, - ] - }; - rs.initiate(config, { force: true }); - rs.status(); -} catch(e) { - rs.status().ok -} // name -> collection name // ttlField -> field to perform ttl on // timeField -> field to index for time queries - +// intersectionField -> field containing intersection id for id queries +// rsuIP -> field containing an rsuIP if available +// expireTime -> the number of seconds after the ttl field at which the record should be deleted const collections = [ - {name: "OdeBsmJson", ttlField: "recordGeneratedAt", timeField: "metadata.odeReceivedAt"}, - {name: "OdeMapJson", ttlField: "recordGeneratedAt", timeField: "metadata.odeReceivedAt"}, - {name: "OdeSpatJson", ttlField: "recordGeneratedAt", timeField: "metadata.odeReceivedAt"}, - {name: "OdeTimJson", ttlField: "recordGeneratedAt", timeField: "metadata.odeReceivedAt"}, - {name: "OdePsmJson", ttlField: "recordGeneratedAt", timeField: "metadata.odeReceivedAt"}, -]; -// Function to check if the replica set is ready -function isReplicaSetReady() { - let status; - try { - status = rs.status(); - } catch (error) { - console.error("Error getting replica set status: " + error); - return false; - } + // ODE Json data + {name: "OdeDriverAlertJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: expireSeconds}, + {name: "OdeBsmJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: expireSeconds}, + {name: "OdeBsmJson", "timeField": "recordGeneratedAt", rsuIP:"metadata.originIp", expireTime: expireSeconds}, + {name: "OdeMapJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: expireSeconds}, + {name: "OdeMapJson", "timeField": "recordGeneratedAt", rsuIP:"metadata.originIp", expireTime: expireSeconds}, + {name: "OdeSpatJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: expireSeconds}, + {name: "OdeSpatRxJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: expireSeconds}, + {name: "OdeSrmJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: expireSeconds}, + {name: "OdeSrmJson", "timeField": "recordGeneratedAt", rsuIP:"metadata.originIp", expireTime: expireSeconds}, + {name: "OdeSsmJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: expireSeconds}, + {name: "OdeSsmJson", "timeField": "recordGeneratedAt", rsuIP:"metadata.originIp", expireTime: expireSeconds}, + {name: "OdeTimJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: expireSeconds}, + {name: "OdeTimJson", "timeField": "recordGeneratedAt", rsuIP:"metadata.originIp", expireTime: expireSeconds}, + {name: "OdeTimBroadcastJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: expireSeconds}, + {name: "OdeTIMCertExpirationTimeJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: expireSeconds}, - // Check if the replica set has a primary - if (!status.hasOwnProperty('myState') || status.myState !== 1) { - console.log("Replica set is not ready yet"); - return false; - } + // Ode Raw ASN + {name: "OdeRawEncodedBSMJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: ttlExpireSeconds}, + {name: "OdeRawEncodedMAPJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: ttlExpireSeconds}, + {name: "OdeRawEncodedSPATJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: ttlExpireSeconds}, + {name: "OdeRawEncodedSRMJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: ttlExpireSeconds}, + {name: "OdeRawEncodedSSMJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: ttlExpireSeconds}, + {name: "OdeRawEncodedTIMJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: ttlExpireSeconds}, + + // GeoJson Converter Data + {name: "ProcessedMap", ttlField: "recordGeneratedAt", timeField: "properties.timeStamp", intersectionField: "properties.intersectionId", expireTime: expireSeconds}, + {name: "ProcessedSpat", ttlField: "recordGeneratedAt", timeField: "utcTimeStamp", intersectionField: "intersectionId", expireTime: expireSeconds}, + {name: "ProcessedBsm", ttlField: "recordGeneratedAt", timeField: "timeStamp", geoSpatialField: "features.geometry.coordinates", expireTime: expireSeconds}, + + // Conflict Monitor Events + { name: "CmStopLineStopEvent", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmStopLinePassageEvent", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmIntersectionReferenceAlignmentEvents", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmSignalGroupAlignmentEvents", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmConnectionOfTravelEvent", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmSignalStateConflictEvents", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmLaneDirectionOfTravelEvent", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmSpatTimeChangeDetailsEvent", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmSpatMinimumDataEvents", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmMapBroadcastRateEvents", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmMapMinimumDataEvents", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmSpatBroadcastRateEvents", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CMBsmEvents", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, - console.log("Replica set is ready"); - return true; -} + // Conflict Monitor Assessments + { name: "CmLaneDirectionOfTravelAssessment", ttlField: "assessmentGeneratedAt", timeField: "assessmentGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmConnectionOfTravelAssessment", ttlField: "assessmentGeneratedAt", timeField: "assessmentGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmSignalStateEventAssessment", ttlField: "assessmentGeneratedAt", timeField: "assessmentGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmStopLineStopAssessment", ttlField: "assessmentGeneratedAt", timeField: "assessmentGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + + // Conflict Monitor Notifications + { name: "CmSpatTimeChangeDetailsNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmLaneDirectionOfTravelNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmConnectionOfTravelNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmAppHealthNotifications", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmSignalStateConflictNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmSignalGroupAlignmentNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmStopLinePassageNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmStopLineStopNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds } +]; try{ - - // Wait for the replica set to be ready - while (!isReplicaSetReady()) { - sleep(retry_milliseconds); + db.getMongo().setReadPref("primaryPreferred"); + db = db.getSiblingDB("admin"); + db = db.runCommand({autoCompact: true}); + + // Create Users in Database + for(user of users){ + createUser(user); } - sleep(retry_milliseconds); - // creates another user - console.log("Creating Read Write user..."); - admin = db.getSiblingDB("admin"); - // Check if user already exists - var user = admin.getUser(rw_user); - if (user == null) { - admin.createUser( - { - user: rw_user, - pwd: rw_pass, - roles: [ - { role: "readWrite", db: ode_db }, - ] - } - ); + + db = db.getSiblingDB(DATABASE_NAME); + db.getMongo().setReadPref("primaryPreferred"); + var isMaster = db.isMaster(); + if (isMaster.primary) { + console.log("Connected to the primary replica set member."); } else { - console.log("User \"" + rw_user + "\" already exists."); + console.log("Not connected to the primary replica set member. Current node: " + isMaster.host); } - -} catch (error) { - print("Error connecting to the MongoDB instance: " + error); +} +catch(err){ + console.log("Could not switch DB to Sibling DB"); + console.log(err); } + // Wait for the collections to exist in mongo before trying to create indexes on them let missing_collection_count; -const db = db.getSiblingDB(ode_db); do { try { missing_collection_count = 0; - const collection_names = db.getCollectionNames(); + const collectionNames = db.getCollectionNames(); for (collection of collections) { - console.log("Creating Indexes for Collection" + collection["name"]); - // Create Collection if It doesn't exist + // Create Collection if it doesn't exist let created = false; - if(!collection_names.includes(collection.name)){ + if(!collectionNames.includes(collection.name)){ created = createCollection(collection); // created = true; }else{ @@ -117,30 +153,47 @@ do { } if(created){ - if (collection.hasOwnProperty('ttlField') && collection.ttlField !== 'none') { - createTTLIndex(collection); - } - - + createTTLIndex(collection); + createTimeIntersectionIndex(collection); + createTimeRsuIpIndex(collection); + createGeoSpatialIndex(collection); }else{ missing_collection_count++; console.log("Collection " + collection.name + " does not exist yet"); } } if (missing_collection_count > 0) { - print("Waiting on " + missing_collection_count + " collections to be created...will try again in " + retry_milliseconds + " ms"); - sleep(retry_milliseconds); + console.log("Waiting on " + missing_collection_count + " collections to be created...will try again in " + retryMilliseconds + " ms"); + sleep(retryMilliseconds); } } catch (err) { console.log("Error while setting up TTL indexes in collections"); console.log(rs.status()); console.error(err); - sleep(retry_milliseconds); + sleep(retryMilliseconds); } } while (missing_collection_count > 0); console.log("Finished Creating All TTL indexes"); +function createUser(user){ + try{ + console.log("Creating User: " + user.username + " with Permissions: " + user.roles); + db.createUser( + { + user: user.username, + pwd: user.password, + roles: user.permissions.map(permission => ({ + role: permission.role, + db: permission.database + })) + }); + + }catch (err){ + console.log(err); + console.log("Unable to Create User. Perhaps the User already exists."); + } +} function createCollection(collection){ try { @@ -155,43 +208,178 @@ function createCollection(collection){ // Create TTL Indexes function createTTLIndex(collection) { - if (ttlIndexExists(collection)) { - console.log("TTL index already exists for " + collection.name); + try{ + if(collection.hasOwnProperty("ttlField") && collection.ttlField != null){ + const ttlField = collection.ttlField; + const collectionName = collection.name; + const duration = collection.expireTime; + + let indexJson = {}; + indexJson[ttlField] = 1; + + if (ttlIndexExists(collection)) { + db.runCommand({ + "collMod": collectionName, + "index": { + keyPattern: indexJson, + expireAfterSeconds: duration + } + }); + console.log("Updated TTL index for " + collectionName + " using the field: " + ttlField + " as the timestamp"); + }else{ + db[collectionName].createIndex(indexJson, + {expireAfterSeconds: duration} + ); + console.log("Created TTL index for " + collectionName + " using the field: " + ttlField + " as the timestamp"); + } + } + } catch(err){ + console.log("Failed to Create or Update index for " + collectionName + "using the field: " + ttlField + " as the timestamp"); + } +} + +function createTimeIndex(collection){ + if(timeIndexExists(collection)){ + // Skip if Index already Exists return; } - const collection_name = collection.name; - const timeField = collection.ttlField; + if(collection.hasOwnProperty("timeField") && collection.timeField != null){ + const collectionName = collection.name; + const timeField = collection.timeField; + console.log("Creating Time Index for " + collectionName); - console.log( - "Creating TTL index for " + collection_name + " to remove documents after " + - expire_seconds + - " seconds" - ); + var indexJson = {}; + indexJson[timeField] = -1; - try { - var index_json = {}; - index_json[timeField] = 1; - db[collection_name].createIndex(index_json, - {expireAfterSeconds: expire_seconds} - ); - console.log("Created TTL index for " + collection_name + " using the field: " + timeField + " as the timestamp"); - } catch (err) { - var pattern_json = {}; - pattern_json[timeField] = 1; - db.runCommand({ - "collMod": collection_name, - "index": { - keyPattern: pattern_json, - expireAfterSeconds: expire_seconds - } - }); - console.log("Updated TTL index for " + collection_name + " using the field: " + timeField + " as the timestamp"); + try { + db[collectionName].createIndex(indexJson); + console.log("Created Time Intersection index for " + collectionName + " using the field: " + timeField + " as the timestamp"); + } catch (err) { + db.runCommand({ + "collMod": collectionName, + "index": { + keyPattern: indexJson + } + }); + console.log("Updated Time index for " + collectionName + " using the field: " + timeField + " as the timestamp"); + } } +} +function createTimeRsuIpIndex(){ + if(timeRsuIpIndexExists(collection)){ + // Skip if Index already Exists + return; + } + + if(collection.hasOwnProperty("timeField") && collection.timeField != null && collection.hasOwnProperty("rsuIP") && collection.rsuIP != null){ + const collectionName = collection.name; + const timeField = collection.timeField; + const rsuIP = collection.rsuIP; + console.log("Creating Time rsuIP Index for " + collectionName); + + var indexJson = {}; + indexJson[rsuIP] = -1; + indexJson[timeField] = -1; + + + try { + db[collectionName].createIndex(indexJson); + console.log("Created Time rsuIP Intersection index for " + collectionName + " using the field: " + timeField + " as the timestamp and : " + rsuIP+" as the rsuIP"); + } catch (err) { + db.runCommand({ + "collMod": collectionName, + "index": { + keyPattern: indexJson + } + }); + console.log("Updated Time rsuIP index for " + collectionName + " using the field: " + timeField + " as the timestamp and : " + rsuIP+" as the rsuIP"); + } + } +} + + +function createTimeIntersectionIndex(collection){ + if(timeIntersectionIndexExists(collection)){ + // Skip if Index already Exists + return; + } + + if(collection.hasOwnProperty("timeField") && collection.timeField != null && collection.hasOwnProperty("intersectionField") && collection.intersectionField != null){ + const collectionName = collection.name; + const timeField = collection.timeField; + const intersectionField = collection.intersectionField; + console.log("Creating time intersection index for " + collectionName); + + var indexJson = {}; + indexJson[intersectionField] = -1; + indexJson[timeField] = -1; + + + try { + db[collectionName].createIndex(indexJson); + console.log("Created time intersection index for " + collectionName + " using the field: " + timeField + " as the timestamp and : " + intersectionField + " as the rsuIP"); + } catch (err) { + db.runCommand({ + "collMod": collectionName, + "index": { + keyPattern: indexJson + } + }); + console.log("Updated time intersection index for " + collectionName + " using the field: " + timeField + " as the timestamp and : " + intersectionField + " as the rsuIP"); + } + } } +function createGeoSpatialIndex(collection){ + if(geoSpatialIndexExists(collection)){ + return; + } + + if(collection.hasOwnProperty("timeField") && collection.timeField != null && collection.hasOwnProperty("geoSpatialField") && collection.geoSpatialField != null){ + const collectionName = collection.name; + const timeField = collection.timeField; + const geoSpatialField = collection.geoSpatialField; + console.log("Creating GeoSpatial index for " + collectionName); + + var indexJson = {}; + indexJson[geoSpatialField] = "2dsphere"; + indexJson[timeField] = -1; + + + try { + db[collectionName].createIndex(indexJson); + console.log("Created time geospatial index for " + collectionName + " using the field: " + timeField + " as the timestamp and : " + geoSpatialField + " as the GeoSpatial Field"); + } catch (err) { + db.runCommand({ + "collMod": collectionName, + "index": { + keyPattern: indexJson + } + }); + console.log("Updated time geospatial index for " + collectionName + " using the field: " + timeField + " as the timestamp and : " + geoSpatialField + " as the GeoSpatial Field"); + } + } + +} function ttlIndexExists(collection) { - return db[collection.name].getIndexes().find((idx) => idx.hasOwnProperty('expireAfterSeconds')) !== undefined; -} \ No newline at end of file + return db[collection.name].getIndexes().find((idx) => idx.hasOwnProperty("expireAfterSeconds")) !== undefined; +} + +function timeIntersectionIndexExists(collection){ + return db[collection.name].getIndexes().find((idx) => idx.name == collection.intersectionField + "_-1_" + collection.timeField + "_-1") !== undefined; +} + +function timeRsuIpIndexExists(collection){ + return db[collection.name].getIndexes().find((idx) => idx.name == collection.rsuIP + "_-1_" + collection.timeField + "_-1") !== undefined; +} + +function timeIndexExists(collection){ + return db[collection.name].getIndexes().find((idx) => idx.name == collection.timeField + "_-1") !== undefined; +} + +function geoSpatialIndexExists(collection){ + return db[collection.name].getIndexes().find((idx) => idx.name == collection.geoSpatialField + "_2dsphere_timeStamp_-1") !== undefined; +} diff --git a/mongo/manage_volume.js b/mongo/manage_volume.js new file mode 100644 index 0000000..d30f986 --- /dev/null +++ b/mongo/manage_volume.js @@ -0,0 +1,246 @@ + +// Mongo Data Managment Script + +// Features +// Automatically Logs Collection Sizes +// Automatically Updates Data Retention Periods to prevent overflow +// Performs Emergency Record Deletion when Collections get to large + +// Database to Perform operation on. +const MONGO_DATABASE_NAME = process.env.MONGO_DATABASE_NAME || "CV"; + +// The name of the collection to store the data as. +const MONGO_DATABASE_STORAGE_COLLECTION_NAME = process.env.MONGO_DATABASE_STORAGE_COLLECTION_NAME || "MongoStorage"; + +// Total Size of the database disk in GB. This script will work to ensure all data fits within this store. +const MONGO_DATABASE_SIZE_GB = process.env.MONGO_DATABASE_SIZE_GB || 1000; + +// Specified as a percent of total database size. This is the storage target the database will try to hit. +const MONGO_DATABASE_SIZE_TARGET_PERCENT = process.env.MONGO_DATABASE_SIZE_TARGET_PERCENT || 0.8; + +// Specified as a percent of total database size. +const MONGO_DATABASE_DELETE_THRESHOLD_PERCENT = process.env.MONGO_DATABASE_DELETE_THRESHOLD_PERCENT || 0.9; + +// The maximum amount of time data should be retained. Measured in Seconds. +const MONGO_DATABASE_MAX_TTL_RETENTION_SECONDS = process.env.MONGO_DATABASE_MAX_TTL_RETENTION_SECONDS || 5184000; // 60 Days + +// The minimum amount of time data should be retained. Measured in Seconds. This only effects TTL's set on the data. It will not prevent the database from manual data deletion. +const MONGO_DATABASE_MIN_TTL_RETENTION_SECONDS = process.env.MONGO_DATABASE_MIN_TTL_RETENTION_SECONDS || 604800; // 7 Days + +// When the free space of a collection exceeds this percent of the collections total volume, automatic compaction should occur +const MONGO_DATABASE_COMPACTION_TRIGGER_PERCENT = process.env.MONGO_DATABASE_COMPACTION_TRIGGER_PERCENT || 0.5; + + +const MONGO_ROOT_USERNAME = process.env.MONGO_INITDB_ROOT_USERNAME || "root"; +const MONGO_ROOT_PASSWORD = process.env.MONGO_INITDB_ROOT_PASSWORD || "root"; + +const ENABLE_STORAGE_RECORD = process.env.MONGO_ENABLE_STORAGE_RECORD || true; +const ENABLE_DYNAMIC_TTL = (process.env.MONGO_ENABLE_DYNAMIC_TTL || true) && ENABLE_STORAGE_RECORD; //Storage record must be enabled for Dynamic TTL + + +const MS_PER_HOUR = 60 * 60 * 1000; +const BYTE_TO_GB = 1024 * 1024 * 1024; +const DB_TARGET_SIZE_BYTES = DATABASE_SIZE_GB * DATABASE_SIZE_TARGET_PERCENT * BYTE_TO_GB; +const DB_DELETE_SIZE_BYETS = DATABASE_SIZE_GB * DATABASE_DELETE_THRESHOLD_PERCENT * BYTE_TO_GB; + + + + +print("Managing Mongo Data Volumes"); + +db = db.getSiblingDB("admin"); +db.auth(MONGO_ROOT_USERNAME, MONGO_ROOT_PASSWORD); +db = db.getSiblingDB("ConflictMonitor"); + +class CollectionStats{ + constructor(name, allocatedSpace, freeSpace, indexSpace){ + this.name = name; + this.allocatedSpace = allocatedSpace; + this.freeSpace = freeSpace; + this.indexSize = indexSpace; + } +} + + +class StorageRecord{ + constructor(collectionStats, totalAllocatedStorage, totalFreeSpace, totalIndexSize){ + this.collectionStats = collectionStats; + this.recordGeneratedAt = ISODate(); + this.totalAllocatedStorage = totalAllocatedStorage; + this.totalFreeSpace = totalFreeSpace; + this.totalIndexSize = totalIndexSize; + this.totalSize = totalAllocatedStorage + totalFreeSpace + totalIndexSize; + } +} + +function ema_deltas(records){ + const a = 0.5; + let average_delta = 0; + + for(let i=0; i< records.length-1; i++){ + const delta = records[i+1] - records[i]; + average_delta += Math.pow(a, records.length -i -1) * delta; + } + + return average_delta; + +} + +function updateTTL(){ + + print("Updating TTL") + const ttl = getLatestTTL(); + if(ttl == 0){ + print("Skipping TTL Update") + // Do not update TTL's + return; + } + + + const newestRecords = db.getCollection(DATABASE_STORAGE_COLLECTION_NAME).find().sort({"recordGeneratedAt":-1}).limit(10); + + let sizes = []; + newestRecords.forEach(doc => { + let total = 0; + for(let i=0; i < doc.collectionStats.length; i++){ + total += doc.collectionStats[i].allocatedSpace + doc.collectionStats[i].freeSpace + doc.collectionStats[i].indexSize; + } + + sizes.push(total); + }); + + + // Overshoot Prevention + const growth = ema_deltas(sizes); + const oldestSpat = db.getCollection("ProcessedSpat").find().sort({"recordGeneratedAt":1}).limit(1); + + let new_ttl = ttl; + let possible_ttl = ttl; + + // Check if collection is still growing to capacity, or if it in steady state + if(oldestSpat.recordGeneratedAt > ISODate() - ttl + MS_PER_HOUR && growth > 0){ + possible_ttl = DB_TARGET_SIZE_BYTES / growth; + }else{ + possible_ttl = 3600 * ((DB_TARGET_SIZE_BYTES - sizes[0])/BYTE_TO_GB) + ttl; // Shift the TTL by roughly 1 hour for every GB of data over or under + } + + // Clamp TTL and assign to new TTL; + + if(!isNaN(possible_ttl) && possible_ttl != 0){ + if(possible_ttl > DATABASE_MAX_TTL_RETENTION_SECONDS){ + new_ttl = DATABASE_MAX_TTL_RETENTION_SECONDS; + }else if(possible_ttl < DATABASE_MIN_TTL_RETENTION_SECONDS){ + new_ttl = DATABASE_MIN_TTL_RETENTION_SECONDS; + }else{ + new_ttl = Math.round(possible_ttl); + } + new_ttl = Number(new_ttl); + print("Calculated New TTL for MongoDB: " + new_ttl); + applyNewTTL(new_ttl); + }else{ + print("Not Updating TTL New TTL is NaN"); + } +} + +function getLatestTTL(){ + const indexes = db.getCollection("ProcessedSpat").getIndexes(); + for (let i=0; i < indexes.length; i++){ + if(indexes[i].hasOwnProperty("expireAfterSeconds")){ + return indexes[i]["expireAfterSeconds"]; + } + } + return 0; +} + +function getTTLKey(collection){ + const indexes = db.getCollection(collection).getIndexes(); + for (let i=0; i < indexes.length; i++){ + if(indexes[i].hasOwnProperty("expireAfterSeconds")){ + return [indexes[i]["key"], indexes[i]["expireAfterSeconds"]]; + } + } + return [null, null]; +} + +function applyNewTTL(ttl){ + var collections = db.getCollectionNames(); + for(let i=0; i< collections.length; i++){ + const collection = collections[i]; + let [key, oldTTL] = getTTLKey(collection); + if(oldTTL != ttl && key != null){ + print("Updating TTL For Collection: " + collection, ttl); + db.runCommand({ + "collMod": collection, + "index": { + keyPattern: key, + expireAfterSeconds: ttl + }}); + } + } +} + + +function addNewStorageRecord(){ + var collections = db.getCollectionNames(); + let totalAllocatedStorage = 0; + let totalFreeSpace = 0; + let totalIndexSize = 0; + + let records = []; + + for (var i = 0; i < collections.length; i++) { + let stats = db.getCollection(collections[i]).stats(); + let colStats = db.runCommand({"collstats": collections[i]}); + let blockManager = colStats["wiredTiger"]["block-manager"]; + + let freeSpace = Number(blockManager["file bytes available for reuse"]); + let allocatedStorage = Number(blockManager["file size in bytes"]); + let indexSize = Number(stats.totalIndexSize); + + records.push(new CollectionStats(collections[i], allocatedStorage, freeSpace, indexSize)); + + totalAllocatedStorage += allocatedStorage + totalFreeSpace += freeSpace; + totalIndexSize += indexSize; + + print(collections[i], allocatedStorage / BYTE_TO_GB, freeSpace/ BYTE_TO_GB, indexSize / BYTE_TO_GB); + } + + const storageRecord = new StorageRecord(records, totalAllocatedStorage, totalFreeSpace, totalIndexSize); + db.getCollection(DATABASE_STORAGE_COLLECTION_NAME).insertOne(storageRecord); +} + +function compactCollections(){ + print("Checking Collection Compaction"); + + var collections = db.getCollectionNames(); + + let activeCompactions = []; + db.currentOp({ "active": true, "secs_running": { "$gt": 0 } }).inprog.forEach(op => { + if (op.msg && op.msg.includes("compact")) { + print("Found Active Compactions"); + activeCompactions.push(op.command.compact); + } + }); + + for (var i = 0; i < collections.length; i++) { + let colStats = db.runCommand({"collstats": collections[i]}); + let blockManager = colStats["wiredTiger"]["block-manager"]; + + let freeSpace = Number(blockManager["file bytes available for reuse"]); + let allocatedStorage = Number(blockManager["file size in bytes"]); + + // If free space makes up a significant proportion of allocated storage + if(freeSpace > allocatedStorage * 0.5 && allocatedStorage > (1 * BYTE_TO_GB)){ + if(!activeCompactions.includes(collections[i])){ + print("Compacting Collection", collections[i]); + db.runCommand({compact: collections[i], force:true}); + }else{ + print("Skipping Compaction, Collection Compaction is already scheduled"); + } + } + } +} + +addNewStorageRecord(); +updateTTL(); diff --git a/sample.env b/sample.env index 30f4938..a19d602 100644 --- a/sample.env +++ b/sample.env @@ -54,6 +54,10 @@ MONGO_ADMIN_DB_PASS= MONGO_READ_WRITE_USER=ode MONGO_READ_WRITE_PASS= +MONGO_READ_USER=user +MONGO_READ_PASS= + + MONGO_PORT=27017 MONGO_URI=mongodb://${MONGO_READ_WRITE_USER}:${MONGO_READ_WRITE_PASS}@${MONGO_IP}:${MONGO_PORT}/?directConnection=true MONGO_COLLECTION_TTL=7 # days @@ -61,9 +65,15 @@ MONGO_COLLECTION_TTL=7 # days MONGO_EXPRESS_USER=${MONGO_ADMIN_DB_USER} MONGO_EXPRESS_PASS=${MONGO_ADMIN_DB_PASS} + + # Relative path to the MongoDB init script, upper level directories are supported MONGO_SETUP_SCRIPT_RELATIVE_PATH="./mongo/setup_mongo.sh" MONGO_CREATE_INDEXES_SCRIPT_RELATIVE_PATH="./mongo/create_indexes.js" +MONGO_MANAGE_VOLUMES_SCRIPT_RELATIVE_PATH="./mongo/manage_volume.js" + + + ### MONGODB variables - END ### ### Kafka connect variables - START ### From addb94c1b0aa168d2c129ecc75f7928bcfed6dbf Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:28:04 -0600 Subject: [PATCH 04/44] updates to kafka connect variables --- sample.env | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sample.env b/sample.env index 30f4938..ade295e 100644 --- a/sample.env +++ b/sample.env @@ -70,4 +70,13 @@ MONGO_CREATE_INDEXES_SCRIPT_RELATIVE_PATH="./mongo/create_indexes.js" # NOTE: Required variables: [MONGODB, KAFKA] # Kafka connect log level CONNECT_LOG_LEVEL=ERROR + +CONNECT_TASKS_MAX=1 +CONNECT_CREATE_ODE=true # Create kafka connectors to MongoDB for ODE +CONNECT_CREATE_GEOJSONCONVERTER=true # Create kafka connectors to MongoDB for GeoJSON Converter +CONNECT_CREATE_CONFLICTMONITOR=true # Create kafka connectors to MongoDB for Conflict Monitor +CONNECT_CREATE_DEDUPLICATOR=true # Create kafka connectors to MongoDB for Deduplicator +# Relative path to the Kafka Connector yaml configuration script, upper level directories are supported +# NOTE: This script is used to create kafka connectors +CONNECT_CONFIG_RELATIVE_PATH="./kafka/kafka-connectors-values.yaml" ### Kafka connect variables - END ### \ No newline at end of file From a14554230fa9c94b3051a114fee8b88c13e00b16 Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:07:03 -0600 Subject: [PATCH 05/44] fixed mongo health check --- docker-compose-mongo.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose-mongo.yml b/docker-compose-mongo.yml index aefc4f9..fc11db1 100644 --- a/docker-compose-mongo.yml +++ b/docker-compose-mongo.yml @@ -31,10 +31,10 @@ services: volumes: - mongo_data:/data/db healthcheck: - test: | - echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet + test: "echo 'db.runCommand(\"ping\").ok' | mongosh --quiet --username ${MONGO_ADMIN_DB_USER} --password ${MONGO_ADMIN_DB_PASS} --authenticationDatabase admin --eval \"rs.status().ok\"" interval: 10s - start_period: 30s + timeout: 5s + retries: 5 mongo-setup: profiles: From cf44551a606529f34ccd6695df4e653beac24c8e Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:10:49 -0600 Subject: [PATCH 06/44] updates to create kafka connectors --- README.md | 56 +++++++++++++++++---------- docker-compose-connect.yml | 50 +++++++++++++++++++----- docker-compose-kafka.yml | 9 +++-- kafka/Dockerfile.jikkou | 7 +++- kafka/kafka-connectors-template.jinja | 8 ++-- kafka/kafka-topics-template.jinja | 29 +++++++++++--- kafka/kafka-topics-values.yaml | 20 +++++++++- kafka/kafka_connector_init.sh | 25 ++++++++++++ kafka/kafka_init.sh | 31 +-------------- sample.env | 7 +++- 10 files changed, 166 insertions(+), 76 deletions(-) create mode 100644 kafka/kafka_connector_init.sh diff --git a/README.md b/README.md index c1c0580..5955f08 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ The JPO ITS utilities repository serves as a central location for deploying open - [Quick Run](#quick-run-1) - [4. MongoDB Kafka Connect](#4-mongodb-kafka-connect) - [Configuration](#configuration) + - [Configure Kafka Connector Creation](#configure-kafka-connector-creation) - [Quick Run](#quick-run-2) @@ -103,8 +104,7 @@ The following enviroment variables can be used to configure Kafka Topic creation | `KAFKA_TOPIC_MIN_INSYNC_REPLICAS` | Minumum number of in-sync replicas (for use with ack=all) | | `KAFKA_TOPIC_RETENTION_MS` | Retention time for stream topics, milliseconds | | `KAFKA_TOPIC_DELETE_RETENTION_MS` | Tombstone retention time for compacted topics, milliseconds | - - +| `KAFKA_TOPIC_CONFIG_RELATIVE_PATH` | Relative path to the Kafka topic yaml configuration script, upper level directories are supported | ### Quick Run @@ -121,34 +121,48 @@ The following enviroment variables can be used to configure Kafka Topic creation ## 4. MongoDB Kafka Connect -The mongo-connector service connects to specified Kafka topics (as defined in the mongo-connector/connect_start.sh script) and deposits these messages to separate collections in the MongoDB Database. The codebase that provides this functionality comes from Confluent using their community licensed [cp-kafka-connect image](https://hub.docker.com/r/confluentinc/cp-kafka-connect). Documentation for this image can be found [here](https://docs.confluent.io/platform/current/connect/index.html#what-is-kafka-connect). +The mongo-connector service connects to specified Kafka topics and deposits these messages to separate collections in the MongoDB Database. The codebase that provides this functionality comes from Confluent using their community licensed [cp-kafka-connect image](https://hub.docker.com/r/confluentinc/cp-kafka-connect). Documentation for this image can be found [here](https://docs.confluent.io/platform/current/connect/index.html#what-is-kafka-connect). ### Configuration -Provided in the mongo-connector directory is a sample configuration shell script ([connect_start.sh](./kafka-connect/connect_start.sh)) that can be used to create kafka connectors to MongoDB. The connectors in kafka connect are defined in the format that follows: - -``` shell -declare -A config_name=([name]="topic_name" [collection]="mongo_collection_name" - [convert_timestamp]=true [timefield]="timestamp" [use_key]=true [key]="key" [add_timestamp]=true) -``` - -The format above describes the basic configuration for configuring a sink connector, this should be placed at the beginning of the connect_start.sh file. In general we recommend to keep the MongoDB collection name the same as the topic name to avoid confusion. Additionally, if there is a top level timefield set `convert_timestamp` to true and then specify the time field name that appears in the message. This will allow MongoDB to transform that message into a date object to allow for TTL creation and reduce message size. To override MongoDB's default message `_id` field, set `use_key` to true and then set the `key` property to "key". The "add_timestamp" field defines whether the connector will add a auto generated timestamp to each document. This allows for creation of Time To Live (TTL) indexes on the collections to help limit collection size growth. -After the sink connector is configured above, then make sure to call the createSink function with the config_name of the configuration like so: - -``` shell -createSink config_name -``` - -This needs to be put after the createSink function definition. To use a different `connect_start.sh` script, pass in the relative path of the new script by overriding the `CONNECT_SCRIPT_RELATIVE_PATH` environmental variable. +Kafka connectors are managed by the Set the `COMPOSE_PROFILES` environmental variable as follows: -- `kafka_connect` will only spin up the `kafka-connect` service in [docker-compose-connect](docker-compose-connect.yml) +- `kafka_connect` will only spin up the `kafka-connect` and `kafka-init` services in [docker-compose-connect](docker-compose-connect.yml) - NOTE: This implies that you will be using a separate Kafka and MongoDB cluster - `kafka_connect_standalone` will run the following: 1. `kafka-connect` service from [docker-compose-connect](docker-compose-connect.yml) - 2. `kafka` service from [docker-compose-kafka](docker-compose-kafka.yml) - 3. `mongo` and `mongo-setup` services from [docker-compose-mongo](docker-compose-mongo.yml) + 2. `kafka-init` service from [docker-compose-connect](docker-compose-connect.yml) + 3. `kafka` service from [docker-compose-kafka](docker-compose-kafka.yml) + 4. `mongo` and `mongo-setup` services from [docker-compose-mongo](docker-compose-mongo.yml) + +### Configure Kafka Connector Creation + +The Kafka connectors created by the `kafka-connect-setup` service are configured in the [kafka-connectors-values.yaml](kafka/kafka-connectors-values.yaml) file. The connectors in that file are organized by the application, and given parameters to define the Kafka -> MongoDB sync connector: + +| Connector Variable | Required | Condition | Description| +|---|---|---|---| +| `topicName` | Yes | Always | The name of the Kafka topic to sync from | +| `collectionName` | Yes | Always | The name of the MongoDB collection to write to | +| `generateTimestamp` | No | Optional | Enable or disable adding a timestamp to each message (true/false) | +| `connectorName` | No | Optional | Override the name of the connector from the `collectionName` to this field instead | +| `useTimestamp` | No | Optional | Converts the `timestampField` field at the top level of the value to a BSON date | +| `timestampField` | No | Required if `useTimestamp` is `true` | The name of the timestamp field at the top level of the message | +| `useKey` | No | Optional | Override the document `_id` field in MongoDB to use a specified `keyField` from the message | +| `keyField` | No | Required if `useKey` is `true` | The name of the key field | + +The following environment variables can be used to configure Kafka Connectors: + +| Environment Variable | Description | +|---|---| +| `CONNECT_LOG_LEVEL` | Kafka connect log level (`OFF`, `ERROR`, `WARN`, `INFO`) | +| `CONNECT_TASKS_MAX` | Number of concurrent tasks to configure on kafka connectors | +| `CONNECT_CREATE_ODE` | Whether to create kafka connectors for the ODE | +| `CONNECT_CREATE_GEOJSONCONVERTER` | Whether to create topics for the GeojsonConverter | +| `CONNECT_CREATE_CONFLICTMONITOR` | Whether to create kafka connectors for the Conflict Monitor | +| `CONNECT_CREATE_DEDUPLICATOR` | Whether to create topics for the Deduplicator | +| `CONNECT_CONFIG_RELATIVE_PATH` | Relative path to the Kafka connector yaml configuration script, upper level directories are supported | ### Quick Run diff --git a/docker-compose-connect.yml b/docker-compose-connect.yml index 2579d3d..da33bff 100644 --- a/docker-compose-connect.yml +++ b/docker-compose-connect.yml @@ -24,6 +24,8 @@ services: depends_on: mongo: condition: service_healthy + kafka: + condition: service_healthy environment: MONGO_URI: ${MONGO_URI} MONGO_DB_NAME: ${MONGO_DB_NAME} @@ -31,16 +33,16 @@ services: CONNECT_REST_ADVERTISED_HOST_NAME: connect CONNECT_REST_PORT: 8083 CONNECT_GROUP_ID: kafka-connect-group - CONNECT_CONFIG_STORAGE_TOPIC: topic.kafka-connect-configs - CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: 1 - CONNECT_CONFIG_STORAGE_CLEANUP_POLICY: compact + # Topics are created with jikkou in the kafka-setup service + CONNECT_CONFIG_STORAGE_TOPIC: topic.KafkaConnectConfigs + CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: -1 CONNECT_OFFSET_FLUSH_INTERVAL_MS: 10000 - CONNECT_OFFSET_STORAGE_TOPIC: topic.kafka-connect-offsets - CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: 1 + CONNECT_OFFSET_STORAGE_TOPIC: topic.KafkaConnectOffsets + CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: -1 CONNECT_OFFSET_STORAGE_CLEANUP_POLICY: compact - CONNECT_STATUS_STORAGE_TOPIC: topic.kafka-connect-status + CONNECT_STATUS_STORAGE_TOPIC: topic.KafkaConnectStatus CONNECT_STATUS_STORAGE_CLEANUP_POLICY: compact - CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: 1 + CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: -1 CONNECT_KEY_CONVERTER: "org.apache.kafka.connect.json.JsonConverter" CONNECT_VALUE_CONVERTER: "org.apache.kafka.connect.json.JsonConverter" CONNECT_INTERNAL_KEY_CONVERTER: "org.apache.kafka.connect.json.JsonConverter" @@ -48,5 +50,35 @@ services: CONNECT_LOG4J_ROOT_LOGLEVEL: ${CONNECT_LOG_LEVEL} CONNECT_LOG4J_LOGGERS: "org.apache.kafka.connect.runtime.rest=${CONNECT_LOG_LEVEL},org.reflections=${CONNECT_LOG_LEVEL},com.mongodb.kafka=${CONNECT_LOG_LEVEL}" CONNECT_PLUGIN_PATH: /usr/share/confluent-hub-components - # volumes: - # - ${CONNECT_SCRIPT_RELATIVE_PATH}:/scripts/connect_start.sh \ No newline at end of file + + kafka-connect-setup: + profiles: + - all + - kafka_connect + - kafka_connect_standalone + image: jpo-jikkou + build: + context: kafka + dockerfile: Dockerfile.jikkou + entrypoint: ./kafka_connector_init.sh + restart: on-failure + deploy: + resources: + limits: + cpus: '0.5' + memory: 1G + depends_on: + kafka-connect: + condition: service_healthy + environment: + CONNECT_TASKS_MAX: ${CONNECT_TASKS_MAX} + CONNECT_CREATE_ODE: ${CONNECT_CREATE_ODE} + CONNECT_CREATE_GEOJSONCONVERTER: ${CONNECT_CREATE_GEOJSONCONVERTER} + CONNECT_CREATE_CONFLICTMONITOR: ${CONNECT_CREATE_CONFLICTMONITOR} + CONNECT_CREATE_DEDUPLICATOR: ${CONNECT_CREATE_DEDUPLICATOR} + MONGO_CONNECTOR_USERNAME: ${MONGO_ADMIN_DB_USER} + MONGO_CONNECTOR_PASSWORD: ${MONGO_ADMIN_DB_PASS} + MONGO_DB_IP: ${MONGO_IP} + MONGO_DB_NAME: ${MONGO_DB_NAME} + volumes: + - ${CONNECT_CONFIG_RELATIVE_PATH-./kafka/kafka-connectors-values.yaml}:/app/kafka-connectors-values.yaml \ No newline at end of file diff --git a/docker-compose-kafka.yml b/docker-compose-kafka.yml index 1cf8792..942ab43 100644 --- a/docker-compose-kafka.yml +++ b/docker-compose-kafka.yml @@ -37,6 +37,7 @@ services: KAFKA_CFG_DELETE_TOPIC_ENABLE: "true" KAFKA_CFG_LOG_RETENTION_HOURS: ${KAFKA_LOG_RETENTION_HOURS} KAFKA_CFG_LOG_RETENTION_BYTES: ${KAFKA_LOG_RETENTION_BYTES} + KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: "false" logging: options: max-size: "10m" @@ -48,10 +49,11 @@ services: - all - kafka_full - kafka_setup - image: kafka-setup + image: jpo-jikkou build: context: kafka dockerfile: Dockerfile.jikkou + entrypoint: ./kafka_init.sh restart: on-failure deploy: resources: @@ -76,8 +78,7 @@ services: MONGO_DB_IP: ${MONGO_IP} MONGO_DB_NAME: ${MONGO_DB_NAME} volumes: - - ./kafka/kafka-connectors-values.yaml:/app/kafka-connectors-values.yaml - - ./kafka/kafka-topics-values.yaml:/app/kafka-topics-values.yaml + - ${KAFKA_TOPIC_CONFIG_RELATIVE_PATH:-./kafka/kafka-topics-values.yaml}:/app/kafka-topics-values.yaml kafka-schema-registry: profiles: @@ -129,6 +130,8 @@ services: DYNAMIC_CONFIG_ENABLED: 'true' KAFKA_CLUSTERS_0_NAME: local KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: ${KAFKA_BOOTSTRAP_SERVERS} + KAFKA_CLUSTERS_0_KAFKACONNECT_0_NAME: kafka-connect + KAFKA_CLUSTERS_0_KAFKACONNECT_0_ADDRESS: http://kafka-connect:8083 volumes: kafka: \ No newline at end of file diff --git a/kafka/Dockerfile.jikkou b/kafka/Dockerfile.jikkou index 9e9fb59..17bb27d 100644 --- a/kafka/Dockerfile.jikkou +++ b/kafka/Dockerfile.jikkou @@ -1,4 +1,4 @@ -FROM streamthoughts/jikkou:0.35.2 +FROM streamthoughts/jikkou:0.35.3 # Root user is required to run the 'jikkou apply' command in the kafka_init.sh script USER root @@ -15,9 +15,12 @@ COPY ./kafka_init.sh /app/kafka_init.sh # kafka connect creation files COPY ./kafka-connectors-template.jinja /app/kafka-connectors-template.jinja +COPY ./kafka_connector_init.sh /app/kafka_connector_init.sh # Create/update topics then exit container -ENTRYPOINT ./kafka_init.sh +# Disabled by default to be applied in the docker-compose.yml instead +# ENTRYPOINT ./kafka_init.sh +# ENTRYPOINT ./kafka_connector_init.sh ## For dev & testing, uncomment to keep the container running to be be able to ## use the Jikkou command line within Docker Desktop: diff --git a/kafka/kafka-connectors-template.jinja b/kafka/kafka-connectors-template.jinja index 08bd183..8f34ca2 100644 --- a/kafka/kafka-connectors-template.jinja +++ b/kafka/kafka-connectors-template.jinja @@ -52,19 +52,19 @@ spec: {% endmacro %} {#------- Create topics for apps with env variable = true ----------#} -{% if system.env.KAFKA_TOPIC_CREATE_ODE %} +{% if system.env.CONNECT_CREATE_ODE %} {{ create_connector(values.apps.ode) }} {% endif %} -{% if system.env.KAFKA_TOPIC_CREATE_GEOJSONCONVERTER %} +{% if system.env.CONNECT_CREATE_GEOJSONCONVERTER %} {{ create_connector(values.apps.geojsonconverter) }} {% endif %} -{% if system.env.KAFKA_TOPIC_CREATE_CONFLICTMONITOR %} +{% if system.env.CONNECT_CREATE_CONFLICTMONITOR %} {{ create_connector(values.apps.conflictmonitor) }} {% endif %} -{% if system.env.KAFKA_TOPIC_CREATE_DEDUPLICATOR %} +{% if system.env.CONNECT_CREATE_DEDUPLICATOR %} {{ create_connector(values.apps.deduplicator) }} {% else %} {{ create_connector(values.apps.ode_duplicated) }} diff --git a/kafka/kafka-topics-template.jinja b/kafka/kafka-topics-template.jinja index 27f348e..e2d1ebc 100644 --- a/kafka/kafka-topics-template.jinja +++ b/kafka/kafka-topics-template.jinja @@ -2,7 +2,7 @@ {% macro create_topics(app) %} {# Stream Topics #} -{% for topicName in app.streamTopics %} +{% for topicName in app.streamTopics | default([]) %} --- apiVersion: "kafka.jikkou.io/v1beta2" kind: KafkaTopic @@ -20,7 +20,7 @@ spec: {% endfor %} {# Table Topics #} -{% for topicName in app.tableTopics %} +{% for topicName in app.tableTopics | default([]) %} --- apiVersion: "kafka.jikkou.io/v1beta2" kind: KafkaTopic @@ -37,8 +37,28 @@ spec: delete.retention.ms: {{ system.env.KAFKA_TOPIC_DELETE_RETENTION_MS | default(values.deleteRetentionMs) }} {% endfor %} +{# macro not needed at the moment but it allows for more custom topic creation #} +{% for topic in app.customTopics | default([]) %} +--- +apiVersion: "kafka.jikkou.io/v1beta2" +kind: KafkaTopic +metadata: + name: "{{ topic.topicName }}" + labels: + app: "{{ app.name }}" +spec: + partitions: {{ topic.partitions | default(values.partitions) }} + replicas: {{ system.env.KAFKA_TOPIC_REPLICAS | default(values.replicas) }} + configs: + cleanup.policy: {{ topic.cleanUpPolicy | default(delete) }} + min.insync.replicas: {{ system.env.KAFKA_TOPIC_MIN_INSYNC_REPLICAS | default(values.minInsyncReplicas) }} + delete.retention.ms: {{ system.env.KAFKA_TOPIC_DELETE_RETENTION_MS | default(values.deleteRetentionMs) }} +{% endfor %} + {% endmacro %} +{{ create_topics(values.apps.kafkaconnect) }} + {#------- Create topics for apps with env variable = true ----------#} {% if system.env.KAFKA_TOPIC_CREATE_ODE %} {{ create_topics(values.apps.ode) }} @@ -54,7 +74,4 @@ spec: {% if system.env.KAFKA_TOPIC_CREATE_DEDUPLICATOR %} {{ create_topics(values.apps.deduplicator) }} -{% endif %} - - - +{% endif %} \ No newline at end of file diff --git a/kafka/kafka-topics-values.yaml b/kafka/kafka-topics-values.yaml index f60bdf1..1e05529 100644 --- a/kafka/kafka-topics-values.yaml +++ b/kafka/kafka-topics-values.yaml @@ -82,6 +82,7 @@ apps: - topic.OdePsmTxPojo - topic.OdePsmJson tableTopics: {} + customTopics: {} geojsonconverter: name: jpo-geojsonconverter streamTopics: @@ -90,6 +91,7 @@ apps: - topic.ProcessedMapWKT - topic.ProcessedBsm tableTopics: {} + customTopics: {} conflictmonitor: name: jpo-conflictmonitor streamTopics: @@ -137,6 +139,7 @@ apps: - topic.CmSpatRevisionCounterEvents - topic.CmBsmRevisionCounterEvents - topic.CmTimestampDeltaNotification + customTopics: {} deduplicator: name: jpo-deduplicator streamTopics: @@ -146,4 +149,19 @@ apps: - topic.DeduplicatedOdeTimJson - topic.DeduplicatedOdeRawEncodedTIMJson - topic.DeduplicatedOdeBsmJson - tableTopics: {} \ No newline at end of file + tableTopics: {} + customTopics: {} + kafkaconnect: + name: jpo-kafka-connect + streamTopics: {} + tableTopics: {} + customTopics: + - topicName: topic.KafkaConnectConfigs + partitions: 1 + cleanUpPolicy: compact + - topicName: topic.KafkaConnectOffsets + partitions: 20 + cleanUpPolicy: compact + - topicName: topic.KafkaConnectStatus + partitions: 10 + cleanUpPolicy: compact \ No newline at end of file diff --git a/kafka/kafka_connector_init.sh b/kafka/kafka_connector_init.sh new file mode 100644 index 0000000..c9e13eb --- /dev/null +++ b/kafka/kafka_connector_init.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +echo "CONNECT_CREATE_ODE=$CONNECT_CREATE_ODE" +echo "CONNECT_CREATE_GEOJSONCONVERTER=$CONNECT_CREATE_GEOJSONCONVERTER" +echo "CONNECT_CREATE_CONFLICTMONITOR=$CONNECT_CREATE_CONFLICTMONITOR" +echo "CONNECT_CREATE_DEDUPLICATOR=$CONNECT_CREATE_DEDUPLICATOR" + +# Set the maximum number of retries +MAX_RETRIES=5 +RETRY_COUNT=0 + +# Retry the health check until it is ready or the retry limit is reached +until ./jikkou health get kafkaconnect | yq -e '.status.name == "UP"' > /dev/null; do + echo "Waiting 10 sec for Kafka Connect to be ready (Attempt: $((RETRY_COUNT+1))/$MAX_RETRIES)" + RETRY_COUNT=$((RETRY_COUNT+1)) + sleep 10 +done + +./jikkou validate \ + --files kafka-connectors-template.jinja \ + --values-files kafka-connectors-values.yaml + +./jikkou apply \ + --files kafka-connectors-template.jinja \ + --values-files kafka-connectors-values.yaml \ No newline at end of file diff --git a/kafka/kafka_init.sh b/kafka/kafka_init.sh index d2fd269..ed21935 100644 --- a/kafka/kafka_init.sh +++ b/kafka/kafka_init.sh @@ -12,32 +12,5 @@ echo "KAFKA_TOPIC_CREATE_DEDUPLICATOR=$KAFKA_TOPIC_CREATE_DEDUPLICATOR" # Create or update topics ./jikkou apply \ - --files kafka-topics-template.jinja \ - --values-files kafka-topics-values.yaml - -# Set the maximum number of retries -MAX_RETRIES=5 -RETRY_COUNT=0 - -# Retry the health check until it is ready or the retry limit is reached -# This assumes that if the retry limit is reached that kafka connect is not deployed -until ./jikkou health get kafkaconnect | yq -e '.status.name == "UP"' > /dev/null; do - if [ "$RETRY_COUNT" -ge "$MAX_RETRIES" ]; then - echo "Assuming Kafka Connect is not deployed, exiting." - exit 1 - fi - echo "Waiting 10 sec for Kafka Connect to be ready (Attempt: $((RETRY_COUNT+1))/$MAX_RETRIES)" - RETRY_COUNT=$((RETRY_COUNT+1)) - sleep 10 -done - -./jikkou health get kafkaconnect - -# Run the validate and apply scripts -./jikkou validate \ - --files kafka-connectors-template.jinja \ - --values-files kafka-connectors-values.yaml - -./jikkou apply \ - --files kafka-connectors-template.jinja \ - --values-files kafka-connectors-values.yaml \ No newline at end of file + --files kafka-topics-template.jinja \ + --values-files kafka-topics-values.yaml \ No newline at end of file diff --git a/sample.env b/sample.env index ade295e..628fd47 100644 --- a/sample.env +++ b/sample.env @@ -20,8 +20,10 @@ RESTART_POLICY="on-failure:3" # - kafka # - mongo # - kafka_connect +# - kafka-connect-setup # - kafka_connect # - kafka_connect +# - kafka-connect-setup # EXAMPLE: COMPOSE_PROFILES=kafka_connect_standalone,kafka_ui,mongo_express COMPOSE_PROFILES=all ### COMMON variables - END ### @@ -41,6 +43,9 @@ KAFKA_TOPIC_CREATE_ODE=true # Create topics for ODE KAFKA_TOPIC_CREATE_GEOJSONCONVERTER=true # Create topics for GeoJSON Converter KAFKA_TOPIC_CREATE_CONFLICTMONITOR=true # Create topics for Conflict Monitor KAFKA_TOPIC_CREATE_DEDUPLICATOR=true # Create topics for Deduplicator +# Relative path to the Kafka topic yaml configuration script, upper level directories are supported +# NOTE: This script is used to create kafka topics +KAFKA_TOPIC_CONFIG_RELATIVE_PATH="./kafka/kafka-topics-values.yaml" ### KAFKA variables - END ### ### MONGODB variables - START ### @@ -71,7 +76,7 @@ MONGO_CREATE_INDEXES_SCRIPT_RELATIVE_PATH="./mongo/create_indexes.js" # Kafka connect log level CONNECT_LOG_LEVEL=ERROR -CONNECT_TASKS_MAX=1 +CONNECT_TASKS_MAX=1 # Number of concurrent tasks to configure on kafka connectors CONNECT_CREATE_ODE=true # Create kafka connectors to MongoDB for ODE CONNECT_CREATE_GEOJSONCONVERTER=true # Create kafka connectors to MongoDB for GeoJSON Converter CONNECT_CREATE_CONFLICTMONITOR=true # Create kafka connectors to MongoDB for Conflict Monitor From c00a81484bd6dc4ebbba95c64807871df0a8c619 Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:15:25 -0600 Subject: [PATCH 07/44] remove jikkou debug logs --- kafka/application.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kafka/application.conf b/kafka/application.conf index c8e61fe..9956fce 100644 --- a/kafka/application.conf +++ b/kafka/application.conf @@ -62,7 +62,7 @@ jikkou { # Use when 'authMethod' is 'basicauth' to specify the password for Authorization Basic header basicAuthPassword = null # Enable debug logging - debugLoggingEnabled = true + debugLoggingEnabled = false # # Ssl Config: Use when 'authMethod' is 'ssl' # # The location of the key store file. From 00ceba77e390deab16c4c96c16dbdfc207f3c287 Mon Sep 17 00:00:00 2001 From: john-wiens Date: Wed, 30 Oct 2024 13:10:59 -0600 Subject: [PATCH 08/44] Added supporting files for mongo config --- docker-compose-mongo.yml | 51 ++++++++++--- mongo/create_indexes.js | 154 ++++++++++++++++++++++++--------------- mongo/init_replicas.js | 33 +++++++++ mongo/keyfile.txt | 1 + mongo/manage-volume-cron | 12 +++ mongo/manage_volume.js | 18 +++-- mongo/setup_mongo.sh | 3 +- sample.env | 23 +++++- 8 files changed, 213 insertions(+), 82 deletions(-) create mode 100644 mongo/init_replicas.js create mode 100644 mongo/keyfile.txt create mode 100644 mongo/manage-volume-cron diff --git a/docker-compose-mongo.yml b/docker-compose-mongo.yml index aefc4f9..9b7ca44 100644 --- a/docker-compose-mongo.yml +++ b/docker-compose-mongo.yml @@ -5,7 +5,7 @@ services: - kafka_connect_standalone - mongo_full - mongo - image: mongo:7 + image: mongo:8 hostname: mongo restart: ${RESTART_POLICY} deploy: @@ -18,18 +18,38 @@ services: environment: MONGO_INITDB_ROOT_USERNAME: ${MONGO_ADMIN_DB_USER} MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ADMIN_DB_PASS} - MONGO_INITDB_DATABASE: admin + MONGO_INITDB_DATABASE: admin + MONGO_DATABASE_STORAGE_COLLECTION_NAME: ${MONGO_DATABASE_STORAGE_COLLECTION_NAME} + MONGO_DATABASE_SIZE_GB: ${MONGO_DATABASE_SIZE_GB} + MONGO_DATABASE_SIZE_TARGET_PERCENT: ${MONGO_DATABASE_SIZE_TARGET_PERCENT} + MONGO_DATABASE_DELETE_THRESHOLD_PERCENT: ${MONGO_DATABASE_DELETE_THRESHOLD_PERCENT} + MONGO_DATABASE_MAX_TTL_RETENTION_SECONDS: ${MONGO_DATABASE_MAX_TTL_RETENTION_SECONDS} + MONGO_DATABASE_MIN_TTL_RETENTION_SECONDS: ${MONGO_DATABASE_MIN_TTL_RETENTION_SECONDS} + MONGO_ENABLE_STORAGE_RECORD: ${MONGO_ENABLE_STORAGE_RECORD} + MONGO_ENABLE_DYNAMIC_TTL: ${MONGO_ENABLE_DYNAMIC_TTL} entrypoint: - bash - -c - | - openssl rand -base64 741 > /mongo_keyfile - chmod 400 /mongo_keyfile - chown 999:999 /mongo_keyfile - exec docker-entrypoint.sh $$@ - command: "mongod --bind_ip_all --replSet rs0 --keyFile /mongo_keyfile" + apt update + apt install -y cron gettext systemctl dos2unix + systemctl start cron + systemctl enable cron + envsubst < /data/manage-volume-cron > /etc/cron.d/manage-volume-cron + dos2unix /etc/cron.d/manage-volume-cron + chmod 644 /etc/cron.d/manage-volume-cron + systemctl restart cron + cp /data/keyfile-import.txt /data/keyfile.txt + chmod 400 /data/keyfile.txt + chown 999:999 /data/keyfile.txt + + exec docker-entrypoint.sh $$@ + command: ["mongod", "--replSet", "rs0", "--bind_ip_all", "--keyFile", "/data/keyfile.txt"] volumes: - mongo_data:/data/db + - ./mongo/keyfile.txt:/data/keyfile-import.txt + - .mongo/manage-volume-cron:/data/manage-volume-cron + - ./mongo/manage_volume.js:/data/manage_volume.js healthcheck: test: | echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet @@ -42,7 +62,7 @@ services: - kafka_connect_standalone - mongo_full - mongo - image: mongo:7 + image: mongo:8 hostname: mongo_setup depends_on: - mongo @@ -58,11 +78,21 @@ services: MONGO_DB_NAME: ${MONGO_DB_NAME} MONGO_READ_WRITE_USER: ${MONGO_READ_WRITE_USER} MONGO_READ_WRITE_PASS: ${MONGO_READ_WRITE_PASS} - MONGO_COLLECTION_TTL: ${MONGO_COLLECTION_TTL} + MONGO_READ_USER: ${MONGO_READ_USER} + MONGO_READ_PASS: ${MONGO_READ_PASS} + MONGO_EXPORTER_USERNAME: ${MONGO_EXPORTER_USERNAME} + MONGO_EXPORTER_PASSWORD: ${MONGO_EXPORTER_PASSWORD} + MONGO_DATA_RETENTION_SECONDS: ${MONGO_DATA_RETENTION_SECONDS} + MONGO_ASN_RETENTION_SECONDS: ${MONGO_ASN_RETENTION_SECONDS} + CONNECT_CREATE_GEOJSONCONVERTER: ${CONNECT_CREATE_GEOJSONCONVERTER} + CONNECT_CREATE_CONFLICTMONITOR: ${CONNECT_CREATE_CONFLICTMONITOR} + CONNECT_CREATE_DEDUPLICATOR: ${CONNECT_CREATE_DEDUPLICATOR} + entrypoint: ["/bin/bash", "setup_mongo.sh"] volumes: - ${MONGO_SETUP_SCRIPT_RELATIVE_PATH}:/setup_mongo.sh - ${MONGO_CREATE_INDEXES_SCRIPT_RELATIVE_PATH}:/create_indexes.js + - ${MONGO_INIT_REPLICAS_SCRIPT_RELATIVE_PATH}:/init_replicas.js mongo-express: profiles: @@ -82,12 +112,13 @@ services: depends_on: - mongo environment: - ME_CONFIG_MONGODB_SERVER: "mongo" + # ME_CONFIG_MONGODB_SERVER: "mongo" ME_CONFIG_MONGODB_ENABLE_ADMIN: "true" ME_CONFIG_BASICAUTH_USERNAME: ${MONGO_EXPRESS_USER} ME_CONFIG_BASICAUTH_PASSWORD: ${MONGO_EXPRESS_PASS} ME_CONFIG_MONGODB_ADMINUSERNAME: ${MONGO_ADMIN_DB_USER} ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_ADMIN_DB_PASS} + ME_CONFIG_MONGODB_URL: mongodb://${MONGO_ADMIN_DB_USER}:${MONGO_ADMIN_DB_PASS}@mongo:27017/?authSource=admin&directConnection=true healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8081"] interval: 30s diff --git a/mongo/create_indexes.js b/mongo/create_indexes.js index 39ef00d..1625d17 100644 --- a/mongo/create_indexes.js +++ b/mongo/create_indexes.js @@ -9,35 +9,47 @@ For more information see the header in a_init_replicas.js console.log(""); console.log("Running create_indexes.js"); +Object.keys(process.env).forEach(key => { + console.log(`${key}: ${process.env[key]}`); +}); + // Setup Username and Password Definitions -const MONGO_ROOT_USERNAME = process.env.MONGO_ROOT_USERNAME; -const MONGO_ROOT_PASSWORD = process.env.MONGO_ROOT_PASSWORD; +const MONGO_ROOT_USERNAME = process.env['MONGO_ADMIN_DB_USER']; +const MONGO_ROOT_PASSWORD = process.env['MONGO_ADMIN_DB_PASS']; -const MONGO_READ_WRITE_USER=process.MONGO_READ_WRITE_USER; -const MONGO_READ_WRITE_PASS=process.MONGO_READ_WRITE_PASS; +const MONGO_READ_WRITE_USER=process.env['MONGO_READ_WRITE_USER']; +const MONGO_READ_WRITE_PASS=process.env['MONGO_READ_WRITE_PASS']; -const MONGO_READ_USERNAME = process.env.MONGO_READ_USER; -const MONGO_READ_PASSWORD = process.env.MONGO_READ_PASS; +const MONGO_READ_USER = process.env['MONGO_READ_USER']; +const MONGO_READ_PASS = process.env['MONGO_READ_PASS']; // Prometheus Exporter User -const MONGO_EXPORTER_USERNAME = process.env.MONGO_EXPORTER_USERNAME; -const MONGO_EXPORTER_PASSWORD = process.env.MONGO_EXPORTER_PASSWORD; +const MONGO_EXPORTER_USERNAME = process.env['MONGO_EXPORTER_USERNAME']; +const MONGO_EXPORTER_PASSWORD = process.env['MONGO_EXPORTER_PASSWORD']; -const DATABASE_NAME = process.env.MONGO_DATABASE_NAME || "CV"; +const MONGO_DB_NAME = process.env['MONGO_DB_NAME'] || "CV"; -const expireSeconds = process.env.MONGO_DATA_RETENTION_SECONDS || 5184000; // 2 months -const ttlExpireSeconds = process.env.MONGO_ASN_RETENTION_SECONDS || 86400; // 24 hours +const expireSeconds = Number(process.env['MONGO_DATA_RETENTION_SECONDS']) || 5184000; // 2 months +const ttlExpireSeconds = Number(process.env['MONGO_ASN_RETENTION_SECONDS']) || 86400; // 24 hours const retryMilliseconds = 10000; +const CONNECT_CREATE_ODE = process.env['CONNECT_CREATE_ODE'] || true; +const CONNECT_CREATE_GEOJSONCONVERTER = process.env['CONNECT_CREATE_GEOJSONCONVERTER'] || true; +const CONNECT_CREATE_CONFLICTMONITOR = process.env['CONNECT_CREATE_CONFLICTMONITOR'] || true; +const CONNECT_CREATE_DEDUPLICATOR = process.env['CONNECT_CREATE_DEDUPLICATOR'] || true; + const users = [ // {username: CM_MONGO_ROOT_USERNAME, password: CM_MONGO_ROOT_PASSWORD, roles: "root", database: "admin" }, - {username: MONGO_READ_WRITE_USER, password: MONGO_READ_WRITE_USER, permissions: [{role: "readWrite", database: DATABASE_NAME}]}, - {username: MONGO_USER_USERNAME, password: MONGO_USER_PASSWORD, permissions: [{role: "read", database: DATABASE_NAME}]}, - {username: MONGO_EXPORTER_USERNAME, password: MONGO_EXPORTER_PASSWORD, permissions: [{role: "clusterMonitor", database: "admin"}, {role: "read", database: DATABASE_NAME}]} + {username: MONGO_READ_WRITE_USER, password: MONGO_READ_WRITE_USER, permissions: [{role: "readWrite", database: MONGO_DB_NAME}]}, + {username: MONGO_READ_USER, password: MONGO_READ_PASS, permissions: [{role: "read", database: MONGO_DB_NAME}]}, + {username: MONGO_EXPORTER_USERNAME, password: MONGO_EXPORTER_PASSWORD, permissions: [{role: "clusterMonitor", database: "admin"}, {role: "read", database: MONGO_DB_NAME}]} ]; +console.log("\n\n\n\nMONGO_READ_WRITE_USER: " + MONGO_READ_WRITE_USER); +console.log("MONGO_READ_WRITE_PASS: " + MONGO_READ_WRITE_PASS + "\n\n\n\n"); + // name -> collection name // ttlField -> field to perform ttl on @@ -45,8 +57,7 @@ const users = [ // intersectionField -> field containing intersection id for id queries // rsuIP -> field containing an rsuIP if available // expireTime -> the number of seconds after the ttl field at which the record should be deleted -const collections = [ - +const odeCollections = [ // ODE Json data {name: "OdeDriverAlertJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: expireSeconds}, {name: "OdeBsmJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: expireSeconds}, @@ -71,12 +82,17 @@ const collections = [ {name: "OdeRawEncodedSRMJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: ttlExpireSeconds}, {name: "OdeRawEncodedSSMJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: ttlExpireSeconds}, {name: "OdeRawEncodedTIMJson", ttlField: "recordGeneratedAt", "timeField": "metadata.odeReceivedAt", intersectionField: null, rsuIP:"metadata.originIp", expireTime: ttlExpireSeconds}, - - // GeoJson Converter Data +]; + +// GeoJson Converter Data +const geoJsonConverterCollections = [ {name: "ProcessedMap", ttlField: "recordGeneratedAt", timeField: "properties.timeStamp", intersectionField: "properties.intersectionId", expireTime: expireSeconds}, {name: "ProcessedSpat", ttlField: "recordGeneratedAt", timeField: "utcTimeStamp", intersectionField: "intersectionId", expireTime: expireSeconds}, {name: "ProcessedBsm", ttlField: "recordGeneratedAt", timeField: "timeStamp", geoSpatialField: "features.geometry.coordinates", expireTime: expireSeconds}, - +]; + + +const conflictMonitorCollections = [ // Conflict Monitor Events { name: "CmStopLineStopEvent", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, { name: "CmStopLinePassageEvent", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, @@ -110,17 +126,33 @@ const collections = [ { name: "CmNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds } ]; +let collections = []; + +if(CONNECT_CREATE_ODE){ + collections = collections.concat(odeCollections); +} + +if(CONNECT_CREATE_GEOJSONCONVERTER){ + collections = collections.concat(geoJsonConverterCollections); +} + +if(CONNECT_CREATE_CONFLICTMONITOR){ + collections = collections.concat(conflictMonitorCollections); +} + + try{ + db.getMongo().setReadPref("primaryPreferred"); db = db.getSiblingDB("admin"); - db = db.runCommand({autoCompact: true}); + db.runCommand({autoCompact: true}); // Create Users in Database for(user of users){ createUser(user); } - db = db.getSiblingDB(DATABASE_NAME); + db = db.getSiblingDB(MONGO_DB_NAME); db.getMongo().setReadPref("primaryPreferred"); var isMaster = db.isMaster(); if (isMaster.primary) { @@ -145,9 +177,8 @@ do { for (collection of collections) { // Create Collection if it doesn't exist let created = false; - if(!collectionNames.includes(collection.name)){ + if(!collectionNames.includes(collection['name'])){ created = createCollection(collection); - // created = true; }else{ created = true; } @@ -159,7 +190,7 @@ do { createGeoSpatialIndex(collection); }else{ missing_collection_count++; - console.log("Collection " + collection.name + " does not exist yet"); + console.log("Collection " + collection['name'] + " does not exist yet"); } } if (missing_collection_count > 0) { @@ -178,14 +209,14 @@ console.log("Finished Creating All TTL indexes"); function createUser(user){ try{ - console.log("Creating User: " + user.username + " with Permissions: " + user.roles); + console.log("Creating User: " + user['username'] + " with Permissions: " + user['permissions']); db.createUser( { - user: user.username, - pwd: user.password, - roles: user.permissions.map(permission => ({ - role: permission.role, - db: permission.database + user: user['username'], + pwd: user['password'], + roles: user['permissions'].map(permission => ({ + role: permission['role'], + db: permission['database'] })) }); @@ -197,7 +228,7 @@ function createUser(user){ function createCollection(collection){ try { - db.createCollection(collection.name); + db.createCollection(collection['name']); return true; } catch (err) { console.log("Unable to Create Collection: " + collection.name); @@ -208,15 +239,17 @@ function createCollection(collection){ // Create TTL Indexes function createTTLIndex(collection) { - try{ - if(collection.hasOwnProperty("ttlField") && collection.ttlField != null){ - const ttlField = collection.ttlField; - const collectionName = collection.name; - const duration = collection.expireTime; - - let indexJson = {}; - indexJson[ttlField] = 1; + if(collection.hasOwnProperty("ttlField") && collection['ttlField'] != null){ + const ttlField = collection['ttlField']; + const collectionName = collection['name']; + const duration = collection['expireTime']; + + let indexJson = {}; + indexJson[ttlField] = 1; + + console.log(collectionName, duration, ttlField); + try{ if (ttlIndexExists(collection)) { db.runCommand({ "collMod": collectionName, @@ -227,14 +260,15 @@ function createTTLIndex(collection) { }); console.log("Updated TTL index for " + collectionName + " using the field: " + ttlField + " as the timestamp"); }else{ - db[collectionName].createIndex(indexJson, + db.getCollection(collectionName).createIndex(indexJson, {expireAfterSeconds: duration} ); console.log("Created TTL index for " + collectionName + " using the field: " + ttlField + " as the timestamp"); } + } catch(err){ + console.log("Failed to Create or Update index for " + collectionName + " using the field: " + ttlField + " as the timestamp"); + console.log(err); } - } catch(err){ - console.log("Failed to Create or Update index for " + collectionName + "using the field: " + ttlField + " as the timestamp"); } } @@ -244,9 +278,9 @@ function createTimeIndex(collection){ return; } - if(collection.hasOwnProperty("timeField") && collection.timeField != null){ - const collectionName = collection.name; - const timeField = collection.timeField; + if(collection.hasOwnProperty("timeField") && collection['timeField'] != null){ + const collectionName = collection['name']; + const timeField = collection['timeField']; console.log("Creating Time Index for " + collectionName); var indexJson = {}; @@ -274,9 +308,9 @@ function createTimeRsuIpIndex(){ } if(collection.hasOwnProperty("timeField") && collection.timeField != null && collection.hasOwnProperty("rsuIP") && collection.rsuIP != null){ - const collectionName = collection.name; - const timeField = collection.timeField; - const rsuIP = collection.rsuIP; + const collectionName = collection['name']; + const timeField = collection['timeField']; + const rsuIP = collection['rsuIP']; console.log("Creating Time rsuIP Index for " + collectionName); var indexJson = {}; @@ -307,9 +341,9 @@ function createTimeIntersectionIndex(collection){ } if(collection.hasOwnProperty("timeField") && collection.timeField != null && collection.hasOwnProperty("intersectionField") && collection.intersectionField != null){ - const collectionName = collection.name; - const timeField = collection.timeField; - const intersectionField = collection.intersectionField; + const collectionName = collection['name']; + const timeField = collection['timeField']; + const intersectionField = collection['intersectionField']; console.log("Creating time intersection index for " + collectionName); var indexJson = {}; @@ -337,10 +371,10 @@ function createGeoSpatialIndex(collection){ return; } - if(collection.hasOwnProperty("timeField") && collection.timeField != null && collection.hasOwnProperty("geoSpatialField") && collection.geoSpatialField != null){ - const collectionName = collection.name; - const timeField = collection.timeField; - const geoSpatialField = collection.geoSpatialField; + if(collection.hasOwnProperty("timeField") && collection['timeField'] != null && collection.hasOwnProperty("geoSpatialField") && collection['geoSpatialField'] != null){ + const collectionName = collection['name']; + const timeField = collection['timeField']; + const geoSpatialField = collection['geoSpatialField']; console.log("Creating GeoSpatial index for " + collectionName); var indexJson = {}; @@ -365,21 +399,21 @@ function createGeoSpatialIndex(collection){ } function ttlIndexExists(collection) { - return db[collection.name].getIndexes().find((idx) => idx.hasOwnProperty("expireAfterSeconds")) !== undefined; + return db[collection['name']].getIndexes().find((idx) => idx.hasOwnProperty("expireAfterSeconds")) !== undefined; } function timeIntersectionIndexExists(collection){ - return db[collection.name].getIndexes().find((idx) => idx.name == collection.intersectionField + "_-1_" + collection.timeField + "_-1") !== undefined; + return db[collection['name']].getIndexes().find((idx) => idx.name == collection['intersectionField'] + "_-1_" + collection['timeField'] + "_-1") !== undefined; } function timeRsuIpIndexExists(collection){ - return db[collection.name].getIndexes().find((idx) => idx.name == collection.rsuIP + "_-1_" + collection.timeField + "_-1") !== undefined; + return db[collection['name']].getIndexes().find((idx) => idx.name == collection['rsuIP'] + "_-1_" + collection['timeField'] + "_-1") !== undefined; } function timeIndexExists(collection){ - return db[collection.name].getIndexes().find((idx) => idx.name == collection.timeField + "_-1") !== undefined; + return db[collection['name']].getIndexes().find((idx) => idx.name == collection['timeField'] + "_-1") !== undefined; } function geoSpatialIndexExists(collection){ - return db[collection.name].getIndexes().find((idx) => idx.name == collection.geoSpatialField + "_2dsphere_timeStamp_-1") !== undefined; + return db[collection['name']].getIndexes().find((idx) => idx.name == collection['geoSpatialField'] + "_2dsphere_timeStamp_-1") !== undefined; } diff --git a/mongo/init_replicas.js b/mongo/init_replicas.js new file mode 100644 index 0000000..9578377 --- /dev/null +++ b/mongo/init_replicas.js @@ -0,0 +1,33 @@ + +/* + +This script is the first of two scripts responsible for setting up mongoDB on initial boot. +These scripts should be copied to the /docker-entrypoint-initdb.d/ directory within the docker image; +the docker image will then execute these scripts automatically when the database is first created. +This script and is partner are prefixed with the letters a and b respectively to ensure they are run +in the proper order when copied into the mongoDB docker image. + +Since the conflict monitor uses a replica set in its mongoDB configuration. Initializing the replica set +and configuring the collections are separated from one another. The purpose of this to force a reconnect +to the database. This in turn allows the new connection to be connected to the primary replica which is +required for creating indexes on all the collections. This script is only responsible for creating the +replica set. Almost all other configuration should go in b_create_indexes.js + +Documentation on how the mongoDB docker image runs startup scripts can be found here +https://hub.docker.com/_/mongo/ + +*/ + + + +console.log("Initializing Replicas"); +try{ + db_status = rs.status(); +} catch(err){ + console.log("Initializing New DB"); + try{ + rs.initiate({ _id: 'rs0', members: [{ _id: 0, host: 'localhost:27017' }] }).ok + }catch(err){ + console.log("Unable to Initialize DB"); + } +} diff --git a/mongo/keyfile.txt b/mongo/keyfile.txt new file mode 100644 index 0000000..a32a434 --- /dev/null +++ b/mongo/keyfile.txt @@ -0,0 +1 @@ +1234567890 diff --git a/mongo/manage-volume-cron b/mongo/manage-volume-cron new file mode 100644 index 0000000..bfe0a0a --- /dev/null +++ b/mongo/manage-volume-cron @@ -0,0 +1,12 @@ +MONGO_DATABASE_NAME=${MONGO_DATABASE_NAME} +MONGO_DATABASE_STORAGE_COLLECTION_NAME=${MONGO_DATABASE_STORAGE_COLLECTION_NAME} +MONGO_DATABASE_SIZE_GB=${MONGO_DATABASE_SIZE_GB} +MONGO_DATABASE_SIZE_TARGET_PERCENT=${MONGO_DATABASE_SIZE_TARGET_PERCENT} +MONGO_DATABASE_DELETE_THRESHOLD_PERCENT=${MONGO_DATABASE_DELETE_THRESHOLD_PERCENT} +MONGO_DATABASE_MAX_TTL_RETENTION_SECONDS=${MONGO_DATABASE_MAX_TTL_RETENTION_SECONDS} +MONGO_DATABASE_MIN_TTL_RETENTION_SECONDS=${MONGO_DATABASE_MIN_TTL_RETENTION_SECONDS} +MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME} +MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD} + +* * * * * root mongosh /docker-entrypoint-initdb.d/manage_volume.js > /var/log/cron.log 2>&1 +# An empty line is required at the end of this file for a valid cron file. \ No newline at end of file diff --git a/mongo/manage_volume.js b/mongo/manage_volume.js index d30f986..876024e 100644 --- a/mongo/manage_volume.js +++ b/mongo/manage_volume.js @@ -27,15 +27,12 @@ const MONGO_DATABASE_MAX_TTL_RETENTION_SECONDS = process.env.MONGO_DATABASE_MAX_ // The minimum amount of time data should be retained. Measured in Seconds. This only effects TTL's set on the data. It will not prevent the database from manual data deletion. const MONGO_DATABASE_MIN_TTL_RETENTION_SECONDS = process.env.MONGO_DATABASE_MIN_TTL_RETENTION_SECONDS || 604800; // 7 Days -// When the free space of a collection exceeds this percent of the collections total volume, automatic compaction should occur -const MONGO_DATABASE_COMPACTION_TRIGGER_PERCENT = process.env.MONGO_DATABASE_COMPACTION_TRIGGER_PERCENT || 0.5; - const MONGO_ROOT_USERNAME = process.env.MONGO_INITDB_ROOT_USERNAME || "root"; const MONGO_ROOT_PASSWORD = process.env.MONGO_INITDB_ROOT_PASSWORD || "root"; -const ENABLE_STORAGE_RECORD = process.env.MONGO_ENABLE_STORAGE_RECORD || true; -const ENABLE_DYNAMIC_TTL = (process.env.MONGO_ENABLE_DYNAMIC_TTL || true) && ENABLE_STORAGE_RECORD; //Storage record must be enabled for Dynamic TTL +const MONGO_ENABLE_STORAGE_RECORD = process.env.MONGO_ENABLE_STORAGE_RECORD || true; +const MONGO_ENABLE_DYNAMIC_TTL = (process.env.MONGO_ENABLE_DYNAMIC_TTL || true) && MONGO_ENABLE_STORAGE_RECORD; //Storage record must be enabled for Dynamic TTL const MS_PER_HOUR = 60 * 60 * 1000; @@ -242,5 +239,12 @@ function compactCollections(){ } } -addNewStorageRecord(); -updateTTL(); +if(MONGO_ENABLE_STORAGE_RECORD){ + addNewStorageRecord(); +} + +if(MONGO_ENABLE_DYNAMIC_TTL){ + updateTTL(); +} + + diff --git a/mongo/setup_mongo.sh b/mongo/setup_mongo.sh index 13cb4e7..c6f0a77 100644 --- a/mongo/setup_mongo.sh +++ b/mongo/setup_mongo.sh @@ -8,4 +8,5 @@ echo "MongoDB is up and running!" cd / -mongosh -u $MONGO_ADMIN_DB_USER -p $MONGO_ADMIN_DB_PASS --authenticationDatabase admin --host mongo:27017 /create_indexes.js +mongosh -u $MONGO_ADMIN_DB_USER -p $MONGO_ADMIN_DB_PASS --authenticationDatabase admin --host mongo:27017 /init_replicas.js +mongosh -u $MONGO_ADMIN_DB_USER -p $MONGO_ADMIN_DB_PASS --authenticationDatabase admin --host mongo:27017 /create_indexes.js diff --git a/sample.env b/sample.env index 482f07a..892dbc5 100644 --- a/sample.env +++ b/sample.env @@ -46,7 +46,7 @@ KAFKA_TOPIC_CREATE_DEDUPLICATOR=true # Create topics for Deduplicator ### MONGODB variables - START ### # NOTE: Must set a password for the container to start up properly MONGO_IP=${DOCKER_HOST_IP} -MONGO_DB_NAME=ode +MONGO_DB_NAME=CV MONGO_ADMIN_DB_USER=admin MONGO_ADMIN_DB_PASS= @@ -57,18 +57,33 @@ MONGO_READ_WRITE_PASS= MONGO_READ_USER=user MONGO_READ_PASS= +MONGO_EXPORTER_USERNAME=export +MONGO_EXPORTER_PASSWORD= + +MONGO_EXPRESS_USER=${MONGO_ADMIN_DB_USER} +MONGO_EXPRESS_PASS=${MONGO_ADMIN_DB_PASS} MONGO_PORT=27017 MONGO_URI=mongodb://${MONGO_READ_WRITE_USER}:${MONGO_READ_WRITE_PASS}@${MONGO_IP}:${MONGO_PORT}/?directConnection=true -MONGO_COLLECTION_TTL=7 # days +MONGO_DATA_RETENTION_SECONDS=5184000 +MONGO_ASN_RETENTION_SECONDS=86400 -MONGO_EXPRESS_USER=${MONGO_ADMIN_DB_USER} -MONGO_EXPRESS_PASS=${MONGO_ADMIN_DB_PASS} + +MONGO_DATABASE_STORAGE_COLLECTION_NAME=MongoStorage +MONGO_DATABASE_SIZE_GB=1000 +MONGO_DATABASE_SIZE_TARGET_PERCENT=0.8 +MONGO_DATABASE_DELETE_THRESHOLD_PERCENT=0.9 +MONGO_DATABASE_MAX_TTL_RETENTION_SECONDS=5184000 +MONGO_DATABASE_MIN_TTL_RETENTION_SECONDS=604800 +MONGO_DATABASE_COMPACTION_TRIGGER_PERCENT=MONGO_DATABASE_COMPACTION_TRIGGER_PERCENT +MONGO_ENABLE_STORAGE_RECORD=true +MONGO_ENABLE_DYNAMIC_TTL=true # Relative path to the MongoDB init script, upper level directories are supported MONGO_SETUP_SCRIPT_RELATIVE_PATH="./mongo/setup_mongo.sh" +MONGO_INIT_REPLICAS_SCRIPT_RELATIVE_PATH="./mongo/init_replicas.js" MONGO_CREATE_INDEXES_SCRIPT_RELATIVE_PATH="./mongo/create_indexes.js" MONGO_MANAGE_VOLUMES_SCRIPT_RELATIVE_PATH="./mongo/manage_volume.js" From bb791b885d23a719e39d97d3abef79297afe31da Mon Sep 17 00:00:00 2001 From: john-wiens Date: Wed, 30 Oct 2024 13:43:24 -0600 Subject: [PATCH 09/44] Fixed issues with mongo cron task --- docker-compose-mongo.yml | 4 ++-- mongo/manage-volume-cron | 4 ++-- mongo/manage_volume.js | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docker-compose-mongo.yml b/docker-compose-mongo.yml index 87eff38..dbdf7c6 100644 --- a/docker-compose-mongo.yml +++ b/docker-compose-mongo.yml @@ -27,6 +27,7 @@ services: MONGO_DATABASE_MIN_TTL_RETENTION_SECONDS: ${MONGO_DATABASE_MIN_TTL_RETENTION_SECONDS} MONGO_ENABLE_STORAGE_RECORD: ${MONGO_ENABLE_STORAGE_RECORD} MONGO_ENABLE_DYNAMIC_TTL: ${MONGO_ENABLE_DYNAMIC_TTL} + MONGO_DB_NAME: ${MONGO_DB_NAME} entrypoint: - bash - -c @@ -48,7 +49,7 @@ services: volumes: - mongo_data:/data/db - ./mongo/keyfile.txt:/data/keyfile-import.txt - - .mongo/manage-volume-cron:/data/manage-volume-cron + - ./mongo/manage-volume-cron:/data/manage-volume-cron - ./mongo/manage_volume.js:/data/manage_volume.js healthcheck: test: "echo 'db.runCommand(\"ping\").ok' | mongosh --quiet --username ${MONGO_ADMIN_DB_USER} --password ${MONGO_ADMIN_DB_PASS} --authenticationDatabase admin --eval \"rs.status().ok\"" @@ -112,7 +113,6 @@ services: depends_on: - mongo environment: - # ME_CONFIG_MONGODB_SERVER: "mongo" ME_CONFIG_MONGODB_ENABLE_ADMIN: "true" ME_CONFIG_BASICAUTH_USERNAME: ${MONGO_EXPRESS_USER} ME_CONFIG_BASICAUTH_PASSWORD: ${MONGO_EXPRESS_PASS} diff --git a/mongo/manage-volume-cron b/mongo/manage-volume-cron index bfe0a0a..57ca0fc 100644 --- a/mongo/manage-volume-cron +++ b/mongo/manage-volume-cron @@ -1,4 +1,4 @@ -MONGO_DATABASE_NAME=${MONGO_DATABASE_NAME} +MONGO_DATABASE_NAME=${MONGO_DB_NAME} MONGO_DATABASE_STORAGE_COLLECTION_NAME=${MONGO_DATABASE_STORAGE_COLLECTION_NAME} MONGO_DATABASE_SIZE_GB=${MONGO_DATABASE_SIZE_GB} MONGO_DATABASE_SIZE_TARGET_PERCENT=${MONGO_DATABASE_SIZE_TARGET_PERCENT} @@ -8,5 +8,5 @@ MONGO_DATABASE_MIN_TTL_RETENTION_SECONDS=${MONGO_DATABASE_MIN_TTL_RETENTION_SECO MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME} MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD} -* * * * * root mongosh /docker-entrypoint-initdb.d/manage_volume.js > /var/log/cron.log 2>&1 +* * * * * root mongosh /data/manage_volume.js > /var/log/cron.log 2>&1 # An empty line is required at the end of this file for a valid cron file. \ No newline at end of file diff --git a/mongo/manage_volume.js b/mongo/manage_volume.js index 876024e..899caa8 100644 --- a/mongo/manage_volume.js +++ b/mongo/manage_volume.js @@ -37,8 +37,8 @@ const MONGO_ENABLE_DYNAMIC_TTL = (process.env.MONGO_ENABLE_DYNAMIC_TTL || true) const MS_PER_HOUR = 60 * 60 * 1000; const BYTE_TO_GB = 1024 * 1024 * 1024; -const DB_TARGET_SIZE_BYTES = DATABASE_SIZE_GB * DATABASE_SIZE_TARGET_PERCENT * BYTE_TO_GB; -const DB_DELETE_SIZE_BYETS = DATABASE_SIZE_GB * DATABASE_DELETE_THRESHOLD_PERCENT * BYTE_TO_GB; +const DB_TARGET_SIZE_BYTES = MONGO_DATABASE_SIZE_GB * MONGO_DATABASE_SIZE_TARGET_PERCENT * BYTE_TO_GB; +const DB_DELETE_SIZE_BYETS = MONGO_DATABASE_SIZE_GB * MONGO_DATABASE_DELETE_THRESHOLD_PERCENT * BYTE_TO_GB; @@ -47,7 +47,7 @@ print("Managing Mongo Data Volumes"); db = db.getSiblingDB("admin"); db.auth(MONGO_ROOT_USERNAME, MONGO_ROOT_PASSWORD); -db = db.getSiblingDB("ConflictMonitor"); +db = db.getSiblingDB(MONGO_DATABASE_NAME); class CollectionStats{ constructor(name, allocatedSpace, freeSpace, indexSpace){ @@ -94,7 +94,7 @@ function updateTTL(){ } - const newestRecords = db.getCollection(DATABASE_STORAGE_COLLECTION_NAME).find().sort({"recordGeneratedAt":-1}).limit(10); + const newestRecords = db.getCollection(MONGO_DATABASE_STORAGE_COLLECTION_NAME).find().sort({"recordGeneratedAt":-1}).limit(10); let sizes = []; newestRecords.forEach(doc => { @@ -124,10 +124,10 @@ function updateTTL(){ // Clamp TTL and assign to new TTL; if(!isNaN(possible_ttl) && possible_ttl != 0){ - if(possible_ttl > DATABASE_MAX_TTL_RETENTION_SECONDS){ - new_ttl = DATABASE_MAX_TTL_RETENTION_SECONDS; - }else if(possible_ttl < DATABASE_MIN_TTL_RETENTION_SECONDS){ - new_ttl = DATABASE_MIN_TTL_RETENTION_SECONDS; + if(possible_ttl > MONGO_DATABASE_MAX_TTL_RETENTION_SECONDS){ + new_ttl = MONGO_DATABASE_MAX_TTL_RETENTION_SECONDS; + }else if(possible_ttl < MONGO_DATABASE_MIN_TTL_RETENTION_SECONDS){ + new_ttl = MONGO_DATABASE_MIN_TTL_RETENTION_SECONDS; }else{ new_ttl = Math.round(possible_ttl); } @@ -204,7 +204,7 @@ function addNewStorageRecord(){ } const storageRecord = new StorageRecord(records, totalAllocatedStorage, totalFreeSpace, totalIndexSize); - db.getCollection(DATABASE_STORAGE_COLLECTION_NAME).insertOne(storageRecord); + db.getCollection(MONGO_DATABASE_STORAGE_COLLECTION_NAME).insertOne(storageRecord); } function compactCollections(){ From 250007b0aa22884b04f72f9a6bc0facf0228db56 Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:36:06 -0600 Subject: [PATCH 10/44] updates to allow for connect url configuration and change jikkou to suport remote kafka endpoints --- README.md | 1 + docker-compose-connect.yml | 1 + docker-compose-kafka.yml | 3 ++- kafka/application.conf | 3 ++- sample.env | 2 ++ 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5955f08..9a141e5 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,7 @@ The following environment variables can be used to configure Kafka Connectors: | Environment Variable | Description | |---|---| +| `CONNECT_URL` | Kafka connect API URL | | `CONNECT_LOG_LEVEL` | Kafka connect log level (`OFF`, `ERROR`, `WARN`, `INFO`) | | `CONNECT_TASKS_MAX` | Number of concurrent tasks to configure on kafka connectors | | `CONNECT_CREATE_ODE` | Whether to create kafka connectors for the ODE | diff --git a/docker-compose-connect.yml b/docker-compose-connect.yml index da33bff..95b3fd3 100644 --- a/docker-compose-connect.yml +++ b/docker-compose-connect.yml @@ -71,6 +71,7 @@ services: kafka-connect: condition: service_healthy environment: + CONNECT_URL: ${CONNECT_URL} CONNECT_TASKS_MAX: ${CONNECT_TASKS_MAX} CONNECT_CREATE_ODE: ${CONNECT_CREATE_ODE} CONNECT_CREATE_GEOJSONCONVERTER: ${CONNECT_CREATE_GEOJSONCONVERTER} diff --git a/docker-compose-kafka.yml b/docker-compose-kafka.yml index 942ab43..e93add9 100644 --- a/docker-compose-kafka.yml +++ b/docker-compose-kafka.yml @@ -64,6 +64,7 @@ services: kafka: condition: service_healthy environment: + KAFKA_BOOTSTRAP_SERVERS: ${KAFKA_BOOTSTRAP_SERVERS} KAFKA_TOPIC_PARTITIONS: ${KAFKA_TOPIC_PARTITIONS} KAFKA_TOPIC_REPLICAS: ${KAFKA_TOPIC_REPLICAS} KAFKA_TOPIC_MIN_INSYNC_REPLICAS: ${KAFKA_TOPIC_MIN_INSYNC_REPLICAS} @@ -131,7 +132,7 @@ services: KAFKA_CLUSTERS_0_NAME: local KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: ${KAFKA_BOOTSTRAP_SERVERS} KAFKA_CLUSTERS_0_KAFKACONNECT_0_NAME: kafka-connect - KAFKA_CLUSTERS_0_KAFKACONNECT_0_ADDRESS: http://kafka-connect:8083 + KAFKA_CLUSTERS_0_KAFKACONNECT_0_ADDRESS: ${CONNECT_URL} volumes: kafka: \ No newline at end of file diff --git a/kafka/application.conf b/kafka/application.conf index 9956fce..54635df 100644 --- a/kafka/application.conf +++ b/kafka/application.conf @@ -22,7 +22,7 @@ jikkou { # The default Kafka Client configuration client { bootstrap.servers = "kafka:9092" - bootstrap.servers = ${?JIKKOU_DEFAULT_KAFKA_BOOTSTRAP_SERVERS} + bootstrap.servers = ${?KAFKA_BOOTSTRAP_SERVERS} # security.protocol = "SSL" # ssl.keystore.location = "/tmp/client.keystore.p12" # ssl.keystore.password = "password" @@ -55,6 +55,7 @@ jikkou { name = "kafka-connect" # URL of the Kafka Connect service url = "http://kafka-connect:8083" + url = ${?CONNECT_URL} # Method to use for authenticating on Kafka Connect. Available values are: [none, basicauth, ssl] authMethod = none # Use when 'authMethod' is 'basicauth' to specify the username for Authorization Basic header diff --git a/sample.env b/sample.env index c438451..7fe5f19 100644 --- a/sample.env +++ b/sample.env @@ -98,6 +98,7 @@ MONGO_MANAGE_VOLUMES_SCRIPT_RELATIVE_PATH="./mongo/manage_volume.js" ### Kafka connect variables - START ### # NOTE: Required variables: [MONGODB, KAFKA] +CONNECT_URL=http://${DOCKER_HOST_IP}:8083 # Kafka connect log level CONNECT_LOG_LEVEL=ERROR @@ -109,4 +110,5 @@ CONNECT_CREATE_DEDUPLICATOR=true # Create kafka connectors to MongoDB for # Relative path to the Kafka Connector yaml configuration script, upper level directories are supported # NOTE: This script is used to create kafka connectors CONNECT_CONFIG_RELATIVE_PATH="./kafka/kafka-connectors-values.yaml" + ### Kafka connect variables - END ### \ No newline at end of file From 1e664f9543dc938b24ed330dca76cbc6b79e43e4 Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:52:34 -0600 Subject: [PATCH 11/44] adding topic.FilteredOdeTimJson --- kafka/kafka-topics-values.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/kafka/kafka-topics-values.yaml b/kafka/kafka-topics-values.yaml index 1e05529..de2542a 100644 --- a/kafka/kafka-topics-values.yaml +++ b/kafka/kafka-topics-values.yaml @@ -59,6 +59,7 @@ apps: - topic.OdeTimJsonTMCFiltered - topic.OdeTimBroadcastJson - topic.J2735TimBroadcastJson + - topic.FilteredOdeTimJson - topic.OdeDriverAlertJson - topic.Asn1DecoderInput - topic.Asn1DecoderOutput From ef7bb41bd72d5410de60c67d7133ab3a683ac0f8 Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:52:09 -0600 Subject: [PATCH 12/44] updates to mongo and kafka connect --- docker-compose-connect.yml | 4 +- docker-compose-mongo.yml | 16 ++--- kafka-connect/Dockerfile | 6 +- kafka-connect/connect_start.sh | 105 --------------------------------- kafka-connect/connect_wait.sh | 20 ------- mongo/create_indexes.js | 10 ++-- sample.env | 1 - 7 files changed, 14 insertions(+), 148 deletions(-) delete mode 100644 kafka-connect/connect_start.sh delete mode 100644 kafka-connect/connect_wait.sh diff --git a/docker-compose-connect.yml b/docker-compose-connect.yml index 95b3fd3..b49e7b6 100644 --- a/docker-compose-connect.yml +++ b/docker-compose-connect.yml @@ -27,8 +27,6 @@ services: kafka: condition: service_healthy environment: - MONGO_URI: ${MONGO_URI} - MONGO_DB_NAME: ${MONGO_DB_NAME} CONNECT_BOOTSTRAP_SERVERS: ${KAFKA_BOOTSTRAP_SERVERS} CONNECT_REST_ADVERTISED_HOST_NAME: connect CONNECT_REST_PORT: 8083 @@ -78,7 +76,7 @@ services: CONNECT_CREATE_CONFLICTMONITOR: ${CONNECT_CREATE_CONFLICTMONITOR} CONNECT_CREATE_DEDUPLICATOR: ${CONNECT_CREATE_DEDUPLICATOR} MONGO_CONNECTOR_USERNAME: ${MONGO_ADMIN_DB_USER} - MONGO_CONNECTOR_PASSWORD: ${MONGO_ADMIN_DB_PASS} + MONGO_CONNECTOR_PASSWORD: ${MONGO_ADMIN_DB_PASS:?} MONGO_DB_IP: ${MONGO_IP} MONGO_DB_NAME: ${MONGO_DB_NAME} volumes: diff --git a/docker-compose-mongo.yml b/docker-compose-mongo.yml index dbdf7c6..4f51c5e 100644 --- a/docker-compose-mongo.yml +++ b/docker-compose-mongo.yml @@ -17,7 +17,7 @@ services: - "27017:27017" environment: MONGO_INITDB_ROOT_USERNAME: ${MONGO_ADMIN_DB_USER} - MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ADMIN_DB_PASS} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ADMIN_DB_PASS:?} MONGO_INITDB_DATABASE: admin MONGO_DATABASE_STORAGE_COLLECTION_NAME: ${MONGO_DATABASE_STORAGE_COLLECTION_NAME} MONGO_DATABASE_SIZE_GB: ${MONGO_DATABASE_SIZE_GB} @@ -75,14 +75,14 @@ services: memory: 1G environment: MONGO_ADMIN_DB_USER: ${MONGO_ADMIN_DB_USER} - MONGO_ADMIN_DB_PASS: ${MONGO_ADMIN_DB_PASS} + MONGO_ADMIN_DB_PASS: ${MONGO_ADMIN_DB_PASS:?} MONGO_DB_NAME: ${MONGO_DB_NAME} MONGO_READ_WRITE_USER: ${MONGO_READ_WRITE_USER} - MONGO_READ_WRITE_PASS: ${MONGO_READ_WRITE_PASS} + MONGO_READ_WRITE_PASS: ${MONGO_READ_WRITE_PASS:?} MONGO_READ_USER: ${MONGO_READ_USER} - MONGO_READ_PASS: ${MONGO_READ_PASS} + MONGO_READ_PASS: ${MONGO_READ_PASS:?} MONGO_EXPORTER_USERNAME: ${MONGO_EXPORTER_USERNAME} - MONGO_EXPORTER_PASSWORD: ${MONGO_EXPORTER_PASSWORD} + MONGO_EXPORTER_PASSWORD: ${MONGO_EXPORTER_PASSWORD:?} MONGO_DATA_RETENTION_SECONDS: ${MONGO_DATA_RETENTION_SECONDS} MONGO_ASN_RETENTION_SECONDS: ${MONGO_ASN_RETENTION_SECONDS} CONNECT_CREATE_GEOJSONCONVERTER: ${CONNECT_CREATE_GEOJSONCONVERTER} @@ -115,10 +115,10 @@ services: environment: ME_CONFIG_MONGODB_ENABLE_ADMIN: "true" ME_CONFIG_BASICAUTH_USERNAME: ${MONGO_EXPRESS_USER} - ME_CONFIG_BASICAUTH_PASSWORD: ${MONGO_EXPRESS_PASS} + ME_CONFIG_BASICAUTH_PASSWORD: ${MONGO_EXPRESS_PASS:?} ME_CONFIG_MONGODB_ADMINUSERNAME: ${MONGO_ADMIN_DB_USER} - ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_ADMIN_DB_PASS} - ME_CONFIG_MONGODB_URL: mongodb://${MONGO_ADMIN_DB_USER}:${MONGO_ADMIN_DB_PASS}@mongo:27017/?authSource=admin&directConnection=true + ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_ADMIN_DB_PASS:?} + ME_CONFIG_MONGODB_URL: mongodb://${MONGO_ADMIN_DB_USER}:${MONGO_ADMIN_DB_PASS}@${MONGO_IP}:27017/?authSource=admin&directConnection=true healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8081"] interval: 30s diff --git a/kafka-connect/Dockerfile b/kafka-connect/Dockerfile index 783817a..0ba8d69 100644 --- a/kafka-connect/Dockerfile +++ b/kafka-connect/Dockerfile @@ -1,10 +1,6 @@ FROM confluentinc/cp-kafka-connect:7.7.0 -COPY connect_wait.sh /scripts/connect_wait.sh - # Docs: https://www.mongodb.com/docs/kafka-connector/current/ RUN confluent-hub install --no-prompt mongodb/kafka-connect-mongodb:1.13.0 # Docs: https://docs.confluent.io/platform/current/connect/transforms/overview.html -RUN confluent-hub install --no-prompt confluentinc/connect-transforms:1.4.7 - -# CMD ["bash", "-c", "/scripts/connect_wait.sh"] \ No newline at end of file +RUN confluent-hub install --no-prompt confluentinc/connect-transforms:1.4.7 \ No newline at end of file diff --git a/kafka-connect/connect_start.sh b/kafka-connect/connect_start.sh deleted file mode 100644 index 2a487ec..0000000 --- a/kafka-connect/connect_start.sh +++ /dev/null @@ -1,105 +0,0 @@ -# bin/bash -echo "------------------------------------------" -echo "Kafka connector creation started." -echo "------------------------------------------" - - -declare -A OdeBsmJson=([name]="topic.OdeBsmJson" [collection]="OdeBsmJson" - [convert_timestamp]=false [timefield]="" [use_key]=false [key]="" [add_timestamp]=true) - -declare -A OdeMapJson=([name]="topic.OdeMapJson" [collection]="OdeMapJson" - [convert_timestamp]=false [timefield]="" [use_key]=false [key]="" [add_timestamp]=true) - -declare -A OdeSpatJson=([name]="topic.OdeSpatJson" [collection]="OdeSpatJson" - [convert_timestamp]=false [timefield]="" [use_key]=false [key]="" [add_timestamp]=true) - -declare -A OdeTimJson=([name]="topic.OdeTimJson" [collection]="OdeTimJson" - [convert_timestamp]=false [timefield]="" [use_key]=false [key]="" [add_timestamp]=true) - -declare -A OdePsmJson=([name]="topic.OdePsmJson" [collection]="OdePsmJson" - [convert_timestamp]=false [timefield]="" [use_key]=false [key]="" [add_timestamp]=true) - -function createSink() { - local -n topic=$1 - local name=${topic[name]} - local collection=${topic[collection]} - local timefield=${topic[timefield]} - local convert_timestamp=${topic[convert_timestamp]} - local use_key=${topic[use_key]} - local key=${topic[key]} - local add_timestamp=${topic[add_timestamp]} - - echo "Creating sink connector with parameters:" - echo "name=$name" - echo "collection=$collection" - echo "timefield=$timefield" - echo "convert_timestamp=$convert_timestamp" - echo "use_key=$use_key" - echo "key=$key" - echo "add_timestamp=$add_timestamp" - - local connectConfig=' { - "group.id":"connector-consumer", - "connector.class":"com.mongodb.kafka.connect.MongoSinkConnector", - "tasks.max":3, - "topics":"'$name'", - "connection.uri":"'$MONGO_URI'", - "database":"'$MONGO_DB_NAME'", - "collection":"'$collection'", - "key.converter":"org.apache.kafka.connect.storage.StringConverter", - "key.converter.schemas.enable":false, - "value.converter":"org.apache.kafka.connect.json.JsonConverter", - "value.converter.schemas.enable":false, - "errors.tolerance": "all", - "mongo.errors.tolerance": "all", - "errors.deadletterqueue.topic.name": "", - "errors.log.enable": false, - "errors.log.include.messages": false, - "errors.deadletterqueue.topic.replication.factor": 0' - - - if [ "$convert_timestamp" == true ] - then - local connectConfig=''$connectConfig', - "transforms": "TimestampConverter", - "transforms.TimestampConverter.field": "'$timefield'", - "transforms.TimestampConverter.type": "org.apache.kafka.connect.transforms.TimestampConverter$Value", - "transforms.TimestampConverter.target.type": "Timestamp"' - fi - - if [ "$add_timestamp" == true ] - then - local connectConfig=''$connectConfig', - "transforms": "AddTimestamp,AddedTimestampConverter", - "transforms.AddTimestamp.type": "org.apache.kafka.connect.transforms.InsertField$Value", - "transforms.AddTimestamp.timestamp.field": "recordGeneratedAt", - "transforms.AddedTimestampConverter.field": "recordGeneratedAt", - "transforms.AddedTimestampConverter.type": "org.apache.kafka.connect.transforms.TimestampConverter$Value", - "transforms.AddedTimestampConverter.target.type": "Timestamp"' - fi - - if [ "$use_key" == true ] - then - local connectConfig=''$connectConfig', - "document.id.strategy": "com.mongodb.kafka.connect.sink.processor.id.strategy.PartialValueStrategy", - "document.id.strategy.partial.value.projection.list": "'$key'", - "document.id.strategy.partial.value.projection.type": "AllowList", - "document.id.strategy.overwrite.existing": true' - fi - - local connectConfig=''$connectConfig' }' - - echo " Creating connector with Config : $connectConfig" - - curl -X PUT http://localhost:8083/connectors/MongoSink.${name}/config -H "Content-Type: application/json" -d "$connectConfig" -} - -createSink OdeBsmJson -createSink OdeMapJson -createSink OdeSpatJson -createSink OdeTimJson -createSink OdePsmJson - -echo "----------------------------------" -echo "ODE Kafka connector creation complete!" -echo "----------------------------------" \ No newline at end of file diff --git a/kafka-connect/connect_wait.sh b/kafka-connect/connect_wait.sh deleted file mode 100644 index 421faef..0000000 --- a/kafka-connect/connect_wait.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -/etc/confluent/docker/run & -echo "Waiting for Kafka Connect to start listening on kafka-connect" -while [ $(curl -s -o /dev/null -w %{http_code} http://localhost:8083/connectors) -eq 000 ] ; do - echo -e $(date) " Kafka Connect listener HTTP state: " $(curl -s -o /dev/null -w %{http_code} http://localhost:8083/connectors) " (waiting for 200)" - sleep 5 -done -sleep 10 -echo -e "\n--\n+> Creating Kafka Connect MongoDB sink" - -# Check if connect_start.sh exists -if [ ! -f /scripts/connect_start.sh ]; then - echo "Error: connect_start.sh does not exist, starting without any connectors." -else - echo "Connect_start.sh exists, starting with connectors." - bash /scripts/connect_start.sh -fi - -sleep infinity \ No newline at end of file diff --git a/mongo/create_indexes.js b/mongo/create_indexes.js index 1625d17..09a4dd7 100644 --- a/mongo/create_indexes.js +++ b/mongo/create_indexes.js @@ -42,7 +42,7 @@ const CONNECT_CREATE_DEDUPLICATOR = process.env['CONNECT_CREATE_DEDUPLICATOR'] | const users = [ // {username: CM_MONGO_ROOT_USERNAME, password: CM_MONGO_ROOT_PASSWORD, roles: "root", database: "admin" }, - {username: MONGO_READ_WRITE_USER, password: MONGO_READ_WRITE_USER, permissions: [{role: "readWrite", database: MONGO_DB_NAME}]}, + {username: MONGO_READ_WRITE_USER, password: MONGO_READ_WRITE_PASS, permissions: [{role: "readWrite", database: MONGO_DB_NAME}]}, {username: MONGO_READ_USER, password: MONGO_READ_PASS, permissions: [{role: "read", database: MONGO_DB_NAME}]}, {username: MONGO_EXPORTER_USERNAME, password: MONGO_EXPORTER_PASSWORD, permissions: [{role: "clusterMonitor", database: "admin"}, {role: "read", database: MONGO_DB_NAME}]} ]; @@ -209,7 +209,7 @@ console.log("Finished Creating All TTL indexes"); function createUser(user){ try{ - console.log("Creating User: " + user['username'] + " with Permissions: " + user['permissions']); + console.log("Creating User: " + user['username'] + " with Permissions: " + JSON.stringify(user['permissions'])); db.createUser( { user: user['username'], @@ -219,10 +219,8 @@ function createUser(user){ db: permission['database'] })) }); - - }catch (err){ - console.log(err); - console.log("Unable to Create User. Perhaps the User already exists."); + } catch (error) { + console.error("Error creating user: ", error); } } diff --git a/sample.env b/sample.env index 7fe5f19..78c62c8 100644 --- a/sample.env +++ b/sample.env @@ -69,7 +69,6 @@ MONGO_EXPRESS_USER=${MONGO_ADMIN_DB_USER} MONGO_EXPRESS_PASS=${MONGO_ADMIN_DB_PASS} MONGO_PORT=27017 -MONGO_URI=mongodb://${MONGO_READ_WRITE_USER}:${MONGO_READ_WRITE_PASS}@${MONGO_IP}:${MONGO_PORT}/?directConnection=true MONGO_DATA_RETENTION_SECONDS=5184000 MONGO_ASN_RETENTION_SECONDS=86400 From da3a82ba01448920b6ccdd7814f4abe4c2ca186b Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:21:38 -0600 Subject: [PATCH 13/44] update keyfile generation to use env vars --- docker-compose-mongo.yml | 8 +++++--- sample.env | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docker-compose-mongo.yml b/docker-compose-mongo.yml index 4f51c5e..f97cf64 100644 --- a/docker-compose-mongo.yml +++ b/docker-compose-mongo.yml @@ -28,6 +28,7 @@ services: MONGO_ENABLE_STORAGE_RECORD: ${MONGO_ENABLE_STORAGE_RECORD} MONGO_ENABLE_DYNAMIC_TTL: ${MONGO_ENABLE_DYNAMIC_TTL} MONGO_DB_NAME: ${MONGO_DB_NAME} + MONGO_DB_KEYFILE_STRING: ${MONGO_DB_KEYFILE_STRING:?} entrypoint: - bash - -c @@ -40,6 +41,7 @@ services: dos2unix /etc/cron.d/manage-volume-cron chmod 644 /etc/cron.d/manage-volume-cron systemctl restart cron + echo "$MONGO_DB_KEYFILE_STRING" > /data/keyfile-import.txt cp /data/keyfile-import.txt /data/keyfile.txt chmod 400 /data/keyfile.txt chown 999:999 /data/keyfile.txt @@ -48,14 +50,14 @@ services: command: ["mongod", "--replSet", "rs0", "--bind_ip_all", "--keyFile", "/data/keyfile.txt"] volumes: - mongo_data:/data/db - - ./mongo/keyfile.txt:/data/keyfile-import.txt + # - ./mongo/keyfile.txt:/data/keyfile-import.txt - ./mongo/manage-volume-cron:/data/manage-volume-cron - ./mongo/manage_volume.js:/data/manage_volume.js healthcheck: test: "echo 'db.runCommand(\"ping\").ok' | mongosh --quiet --username ${MONGO_ADMIN_DB_USER} --password ${MONGO_ADMIN_DB_PASS} --authenticationDatabase admin --eval \"rs.status().ok\"" interval: 10s - timeout: 5s - retries: 5 + timeout: 10s + retries: 10 mongo-setup: profiles: diff --git a/sample.env b/sample.env index 78c62c8..e0d0e8e 100644 --- a/sample.env +++ b/sample.env @@ -53,6 +53,10 @@ KAFKA_TOPIC_CONFIG_RELATIVE_PATH="./kafka/kafka-topics-values.yaml" MONGO_IP=${DOCKER_HOST_IP} MONGO_DB_NAME=CV +# Generate a random string for the MongoDB keyfile using the following command: +# $ openssl rand -base64 32 +MONGO_DB_KEYFILE_STRING= + MONGO_ADMIN_DB_USER=admin MONGO_ADMIN_DB_PASS= From 37cb9f0a9e23a2379f096368edbf8fda1e438606 Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:25:28 -0600 Subject: [PATCH 14/44] delete old keyfile implementation --- mongo/keyfile.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 mongo/keyfile.txt diff --git a/mongo/keyfile.txt b/mongo/keyfile.txt deleted file mode 100644 index a32a434..0000000 --- a/mongo/keyfile.txt +++ /dev/null @@ -1 +0,0 @@ -1234567890 From 8d61357c7258f8082b08cba61d64312c8f65867d Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:25:29 -0600 Subject: [PATCH 15/44] updates --- docker-compose-mongo.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose-mongo.yml b/docker-compose-mongo.yml index f97cf64..45aabc0 100644 --- a/docker-compose-mongo.yml +++ b/docker-compose-mongo.yml @@ -50,7 +50,6 @@ services: command: ["mongod", "--replSet", "rs0", "--bind_ip_all", "--keyFile", "/data/keyfile.txt"] volumes: - mongo_data:/data/db - # - ./mongo/keyfile.txt:/data/keyfile-import.txt - ./mongo/manage-volume-cron:/data/manage-volume-cron - ./mongo/manage_volume.js:/data/manage_volume.js healthcheck: From 20187c31ebe16ad0c085fff5d6eb50ea7450c244 Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:25:58 -0700 Subject: [PATCH 16/44] updates to keyfile gen --- docker-compose-mongo.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker-compose-mongo.yml b/docker-compose-mongo.yml index 45aabc0..2f72bcf 100644 --- a/docker-compose-mongo.yml +++ b/docker-compose-mongo.yml @@ -41,8 +41,7 @@ services: dos2unix /etc/cron.d/manage-volume-cron chmod 644 /etc/cron.d/manage-volume-cron systemctl restart cron - echo "$MONGO_DB_KEYFILE_STRING" > /data/keyfile-import.txt - cp /data/keyfile-import.txt /data/keyfile.txt + echo "$MONGO_DB_KEYFILE_STRING" > /data/keyfile.txt chmod 400 /data/keyfile.txt chown 999:999 /data/keyfile.txt From 615efe4d71399f0450f32f5da6321fefa1dce68a Mon Sep 17 00:00:00 2001 From: John-Wiens Date: Thu, 7 Nov 2024 10:00:06 -0700 Subject: [PATCH 17/44] Added Spat and ASN Tim Deduplication --- kafka/kafka-connectors-values.yaml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/kafka/kafka-connectors-values.yaml b/kafka/kafka-connectors-values.yaml index d22afdb..829c554 100644 --- a/kafka/kafka-connectors-values.yaml +++ b/kafka/kafka-connectors-values.yaml @@ -58,9 +58,6 @@ apps: - topicName: topic.OdeSpatJson collectionName: OdeSpatJson generateTimestamp: true - - topicName: topic.OdeRawEncodedTIMJson - collectionName: OdeRawEncodedTIMJson - generateTimestamp: true - topicName: topic.OdeTimJsonTMCFiltered collectionName: OdeTimJsonTMCFiltered generateTimestamp: true @@ -103,12 +100,12 @@ apps: - topicName: topic.OdeBsmJson collectionName: OdeBsmJson generateTimestamp: true + - topicName: topic.OdeRawEncodedTIMJson + collectionName: OdeRawEncodedTIMJson + generateTimestamp: true geojsonconverter: name: geojsonconverter connectors: - - topicName: topic.ProcessedSpat - collectionName: ProcessedSpat - generateTimestamp: true - topicName: topic.ProcessedBsm collectionName: ProcessedBsm generateTimestamp: true @@ -118,6 +115,9 @@ apps: - topicName: topic.ProcessedMap collectionName: ProcessedMap generateTimestamp: true + - topicName: topic.ProcessedSpat + collectionName: ProcessedSpat + generateTimestamp: true deduplicator: name: deduplicator connectors: @@ -133,10 +133,18 @@ apps: collectionName: OdeTimJson generateTimestamp: true connectorName: DeduplicatedOdeTimJson + - topicName: topic.OdeRawEncodedTIMJson + collectionName: OdeTimJson + generateTimestamp: true + connectorName: DeduplicatedOdeRawEncodedTIMJson - topicName: topic.DeduplicatedOdeBsmJson collectionName: OdeBsmJson generateTimestamp: true connectorName: DeduplicatedOdeBsmJson + - topicName: topic.DeduplicatedProcessedSpat + collectionName: ProcessedSpat + generateTimestamp: true + connectorName: DeduplicatedProcessedSpat conflictmonitor: name: conflictmonitor connectors: From 728957013ba12b4cf927f8e6ef3c1cf97b7ab647 Mon Sep 17 00:00:00 2001 From: John-Wiens Date: Thu, 7 Nov 2024 10:27:08 -0700 Subject: [PATCH 18/44] renaming kafka folder to Jikkou --- README.md | 2 +- docker-compose-connect.yml | 4 ++-- docker-compose-kafka.yml | 4 ++-- {kafka => jikkou}/Dockerfile.jikkou | 0 {kafka => jikkou}/application.conf | 0 {kafka => jikkou}/jikkouconfig | 0 {kafka => jikkou}/kafka-connectors-template.jinja | 0 {kafka => jikkou}/kafka-connectors-values.yaml | 0 {kafka => jikkou}/kafka-topics-template.jinja | 0 {kafka => jikkou}/kafka-topics-values.yaml | 0 {kafka => jikkou}/kafka_connector_init.sh | 0 {kafka => jikkou}/kafka_init.sh | 0 sample.env | 4 ++-- 13 files changed, 7 insertions(+), 7 deletions(-) rename {kafka => jikkou}/Dockerfile.jikkou (100%) rename {kafka => jikkou}/application.conf (100%) rename {kafka => jikkou}/jikkouconfig (100%) rename {kafka => jikkou}/kafka-connectors-template.jinja (100%) rename {kafka => jikkou}/kafka-connectors-values.yaml (100%) rename {kafka => jikkou}/kafka-topics-template.jinja (100%) rename {kafka => jikkou}/kafka-topics-values.yaml (100%) rename {kafka => jikkou}/kafka_connector_init.sh (100%) rename {kafka => jikkou}/kafka_init.sh (100%) diff --git a/README.md b/README.md index 9a141e5..0b14450 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ Set the `COMPOSE_PROFILES` environmental variable as follows: ### Configure Kafka Connector Creation -The Kafka connectors created by the `kafka-connect-setup` service are configured in the [kafka-connectors-values.yaml](kafka/kafka-connectors-values.yaml) file. The connectors in that file are organized by the application, and given parameters to define the Kafka -> MongoDB sync connector: +The Kafka connectors created by the `kafka-connect-setup` service are configured in the [kafka-connectors-values.yaml](jikkou/kafka-connectors-values.yaml) file. The connectors in that file are organized by the application, and given parameters to define the Kafka -> MongoDB sync connector: | Connector Variable | Required | Condition | Description| |---|---|---|---| diff --git a/docker-compose-connect.yml b/docker-compose-connect.yml index b49e7b6..c78c49b 100644 --- a/docker-compose-connect.yml +++ b/docker-compose-connect.yml @@ -56,7 +56,7 @@ services: - kafka_connect_standalone image: jpo-jikkou build: - context: kafka + context: jikkou dockerfile: Dockerfile.jikkou entrypoint: ./kafka_connector_init.sh restart: on-failure @@ -80,4 +80,4 @@ services: MONGO_DB_IP: ${MONGO_IP} MONGO_DB_NAME: ${MONGO_DB_NAME} volumes: - - ${CONNECT_CONFIG_RELATIVE_PATH-./kafka/kafka-connectors-values.yaml}:/app/kafka-connectors-values.yaml \ No newline at end of file + - ${CONNECT_CONFIG_RELATIVE_PATH-./jikkou/kafka-connectors-values.yaml}:/app/kafka-connectors-values.yaml \ No newline at end of file diff --git a/docker-compose-kafka.yml b/docker-compose-kafka.yml index e93add9..8d52a0a 100644 --- a/docker-compose-kafka.yml +++ b/docker-compose-kafka.yml @@ -51,7 +51,7 @@ services: - kafka_setup image: jpo-jikkou build: - context: kafka + context: jikkou dockerfile: Dockerfile.jikkou entrypoint: ./kafka_init.sh restart: on-failure @@ -79,7 +79,7 @@ services: MONGO_DB_IP: ${MONGO_IP} MONGO_DB_NAME: ${MONGO_DB_NAME} volumes: - - ${KAFKA_TOPIC_CONFIG_RELATIVE_PATH:-./kafka/kafka-topics-values.yaml}:/app/kafka-topics-values.yaml + - ${KAFKA_TOPIC_CONFIG_RELATIVE_PATH:-./jikkou/kafka-topics-values.yaml}:/app/kafka-topics-values.yaml kafka-schema-registry: profiles: diff --git a/kafka/Dockerfile.jikkou b/jikkou/Dockerfile.jikkou similarity index 100% rename from kafka/Dockerfile.jikkou rename to jikkou/Dockerfile.jikkou diff --git a/kafka/application.conf b/jikkou/application.conf similarity index 100% rename from kafka/application.conf rename to jikkou/application.conf diff --git a/kafka/jikkouconfig b/jikkou/jikkouconfig similarity index 100% rename from kafka/jikkouconfig rename to jikkou/jikkouconfig diff --git a/kafka/kafka-connectors-template.jinja b/jikkou/kafka-connectors-template.jinja similarity index 100% rename from kafka/kafka-connectors-template.jinja rename to jikkou/kafka-connectors-template.jinja diff --git a/kafka/kafka-connectors-values.yaml b/jikkou/kafka-connectors-values.yaml similarity index 100% rename from kafka/kafka-connectors-values.yaml rename to jikkou/kafka-connectors-values.yaml diff --git a/kafka/kafka-topics-template.jinja b/jikkou/kafka-topics-template.jinja similarity index 100% rename from kafka/kafka-topics-template.jinja rename to jikkou/kafka-topics-template.jinja diff --git a/kafka/kafka-topics-values.yaml b/jikkou/kafka-topics-values.yaml similarity index 100% rename from kafka/kafka-topics-values.yaml rename to jikkou/kafka-topics-values.yaml diff --git a/kafka/kafka_connector_init.sh b/jikkou/kafka_connector_init.sh similarity index 100% rename from kafka/kafka_connector_init.sh rename to jikkou/kafka_connector_init.sh diff --git a/kafka/kafka_init.sh b/jikkou/kafka_init.sh similarity index 100% rename from kafka/kafka_init.sh rename to jikkou/kafka_init.sh diff --git a/sample.env b/sample.env index e0d0e8e..df92363 100644 --- a/sample.env +++ b/sample.env @@ -45,7 +45,7 @@ KAFKA_TOPIC_CREATE_CONFLICTMONITOR=true # Create topics for Conflict Monitor KAFKA_TOPIC_CREATE_DEDUPLICATOR=true # Create topics for Deduplicator # Relative path to the Kafka topic yaml configuration script, upper level directories are supported # NOTE: This script is used to create kafka topics -KAFKA_TOPIC_CONFIG_RELATIVE_PATH="./kafka/kafka-topics-values.yaml" +KAFKA_TOPIC_CONFIG_RELATIVE_PATH="./jikkou/kafka-topics-values.yaml" ### KAFKA variables - END ### ### MONGODB variables - START ### @@ -112,6 +112,6 @@ CONNECT_CREATE_CONFLICTMONITOR=true # Create kafka connectors to MongoDB for CONNECT_CREATE_DEDUPLICATOR=true # Create kafka connectors to MongoDB for Deduplicator # Relative path to the Kafka Connector yaml configuration script, upper level directories are supported # NOTE: This script is used to create kafka connectors -CONNECT_CONFIG_RELATIVE_PATH="./kafka/kafka-connectors-values.yaml" +CONNECT_CONFIG_RELATIVE_PATH="./jikkou/kafka-connectors-values.yaml" ### Kafka connect variables - END ### \ No newline at end of file From e7ddd86c0353e97265eb9f8da6b56fce073ab9e0 Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:39:15 -0700 Subject: [PATCH 19/44] chore: update kafka folder name to Jikkou --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b14450..cee506e 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ An optional `kafka-init`, `schema-registry`, and `kafka-ui` instance can be depl ### Configure Topic Creation -The Kafka topics created by the `kafka-setup` service are configured in the [kafka-topics-values.yaml](kafka/kafka-topics-values.yaml) file. The topics in that file are organized by the application, and sorted into "Stream Topics" (those with `cleanup.policy` = `delete`) and "Table Topics" (with `cleanup.policy` = `compact`). +The Kafka topics created by the `kafka-setup` service are configured in the [kafka-topics-values.yaml](jikkou/kafka-topics-values.yaml) file. The topics in that file are organized by the application, and sorted into "Stream Topics" (those with `cleanup.policy` = `delete`) and "Table Topics" (with `cleanup.policy` = `compact`). The following enviroment variables can be used to configure Kafka Topic creation. From 9f44b59706e38f3d10dc4ffa3d87e48cdebe7300 Mon Sep 17 00:00:00 2001 From: Ivan Yourshaw <39739503+iyourshaw@users.noreply.github.com> Date: Fri, 8 Nov 2024 18:17:29 -0700 Subject: [PATCH 20/44] Add aggregation topics --- kafka/kafka-topics-values.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kafka/kafka-topics-values.yaml b/kafka/kafka-topics-values.yaml index de2542a..fb46f75 100644 --- a/kafka/kafka-topics-values.yaml +++ b/kafka/kafka-topics-values.yaml @@ -114,6 +114,8 @@ apps: - topic.CmBsmIntersection - topic.CmKafkaStateChangeEvents - topic.CmTimestampDeltaEvent + - topic.CmSpatMinimumDataEvent + - topic.CmMapMinimumDataEvent tableTopics: - topic.CmLaneDirectionOfTravelNotification - topic.CmConnectionOfTravelNotification From 08a270443e0b05fbfa0deb1d49ed4737f8134dfe Mon Sep 17 00:00:00 2001 From: Ivan Yourshaw <39739503+iyourshaw@users.noreply.github.com> Date: Fri, 8 Nov 2024 18:24:53 -0700 Subject: [PATCH 21/44] Topics --- kafka/kafka-topics-values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kafka/kafka-topics-values.yaml b/kafka/kafka-topics-values.yaml index fb46f75..00a3c64 100644 --- a/kafka/kafka-topics-values.yaml +++ b/kafka/kafka-topics-values.yaml @@ -114,8 +114,8 @@ apps: - topic.CmBsmIntersection - topic.CmKafkaStateChangeEvents - topic.CmTimestampDeltaEvent - - topic.CmSpatMinimumDataEvent - - topic.CmMapMinimumDataEvent + - topic.CmSpatMinimumDataEventAggregation + - topic.CmMapMinimumDataEventAggregation tableTopics: - topic.CmLaneDirectionOfTravelNotification - topic.CmConnectionOfTravelNotification From 705b824179cacf8855c3554a691e780541f99522 Mon Sep 17 00:00:00 2001 From: John-Wiens Date: Tue, 19 Nov 2024 13:56:35 -0700 Subject: [PATCH 22/44] Adding DB entries for Progression Events --- jikkou/kafka-connectors-values.yaml | 12 ++++++++++-- mongo/create_indexes.js | 8 ++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/jikkou/kafka-connectors-values.yaml b/jikkou/kafka-connectors-values.yaml index 829c554..c00babe 100644 --- a/jikkou/kafka-connectors-values.yaml +++ b/jikkou/kafka-connectors-values.yaml @@ -201,8 +201,16 @@ apps: collectionName: CmTimestampDeltaEvent generateTimestamp: true timestampField: eventGeneratedAt - - topicName: topic.CmEventStateProgressionEvent - collectionName: CmEventStateProgressionEvent + - topicName: topic.CmSpatMessageCountProgressionEvents + collectionName: CmSpatMessageCountProgressionEvents + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmMapMessageCountProgressionEvents + collectionName: CmMapMessageCountProgressionEvents + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmBsmMessageCountProgressionEvents + collectionName: CmBsmMessageCountProgressionEvents generateTimestamp: true timestampField: eventGeneratedAt diff --git a/mongo/create_indexes.js b/mongo/create_indexes.js index 09a4dd7..cbf6466 100644 --- a/mongo/create_indexes.js +++ b/mongo/create_indexes.js @@ -108,6 +108,12 @@ const conflictMonitorCollections = [ { name: "CmSpatBroadcastRateEvents", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, { name: "CMBsmEvents", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + + { name: "CmSpatMessageCountProgressionEvents", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmMapMessageCountProgressionEvents", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "CmBsmMessageCountProgressionEvents", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + + // Conflict Monitor Assessments { name: "CmLaneDirectionOfTravelAssessment", ttlField: "assessmentGeneratedAt", timeField: "assessmentGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, { name: "CmConnectionOfTravelAssessment", ttlField: "assessmentGeneratedAt", timeField: "assessmentGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, @@ -126,8 +132,6 @@ const conflictMonitorCollections = [ { name: "CmNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds } ]; -let collections = []; - if(CONNECT_CREATE_ODE){ collections = collections.concat(odeCollections); } From fbb59cc8a8a79ffadae84932ebc52b7d5f40786d Mon Sep 17 00:00:00 2001 From: John-Wiens Date: Tue, 19 Nov 2024 17:01:22 -0700 Subject: [PATCH 23/44] Adding Collection back in --- mongo/create_indexes.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mongo/create_indexes.js b/mongo/create_indexes.js index cbf6466..0553ebe 100644 --- a/mongo/create_indexes.js +++ b/mongo/create_indexes.js @@ -132,6 +132,8 @@ const conflictMonitorCollections = [ { name: "CmNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds } ]; +let collections = []; + if(CONNECT_CREATE_ODE){ collections = collections.concat(odeCollections); } From 71ebdf265b9bceb14dc3f798bcac7cd22f2d330d Mon Sep 17 00:00:00 2001 From: Ivan Yourshaw <39739503+iyourshaw@users.noreply.github.com> Date: Mon, 25 Nov 2024 14:28:39 -0700 Subject: [PATCH 24/44] Add more aggregation topics --- kafka/kafka-topics-values.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kafka/kafka-topics-values.yaml b/kafka/kafka-topics-values.yaml index 00a3c64..7f16195 100644 --- a/kafka/kafka-topics-values.yaml +++ b/kafka/kafka-topics-values.yaml @@ -116,6 +116,11 @@ apps: - topic.CmTimestampDeltaEvent - topic.CmSpatMinimumDataEventAggregation - topic.CmMapMinimumDataEventAggregation + - topic.CmIntersectionReferenceAlignmentEventAggregation + - topic.CmSignalGroupAlignmentEventAggregation + - topic.CmSignalStateConflictEventAggregation + - topic.CmSpatTimeChangeDetailsEventAggregation + - topic.CmEventStateProgressionEventAggregation tableTopics: - topic.CmLaneDirectionOfTravelNotification - topic.CmConnectionOfTravelNotification From 2b690f1d8895540839838cc3aec65c9f872522ae Mon Sep 17 00:00:00 2001 From: Ivan Yourshaw <39739503+iyourshaw@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:41:21 -0700 Subject: [PATCH 25/44] notification and message count agg topics --- kafka/kafka-topics-values.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kafka/kafka-topics-values.yaml b/kafka/kafka-topics-values.yaml index 7f16195..0ddb483 100644 --- a/kafka/kafka-topics-values.yaml +++ b/kafka/kafka-topics-values.yaml @@ -121,6 +121,9 @@ apps: - topic.CmSignalStateConflictEventAggregation - topic.CmSpatTimeChangeDetailsEventAggregation - topic.CmEventStateProgressionEventAggregation + - topic.CmBsmMessageCountProgressionEventAggregation + - topic.CmMapMessageCountProgressionEventAggregation + - topic.CmSpatMessageCountProgressionEventAggregation tableTopics: - topic.CmLaneDirectionOfTravelNotification - topic.CmConnectionOfTravelNotification @@ -147,6 +150,11 @@ apps: - topic.CmSpatRevisionCounterEvents - topic.CmBsmRevisionCounterEvents - topic.CmTimestampDeltaNotification + - topic.CmIntersectionReferenceAlignmentNotificationAggregation + - topic.CmSignalGroupAlignmentNotificationAggregation + - topic.CmSignalStateConflictNotificationAggregation + - topic.CmSpatTimeChangeDetailsNotificationAggregation + - topic.CmEventStateProgressionNotificationAggregation customTopics: {} deduplicator: name: jpo-deduplicator From 690812c27e71ec3d42cf0c264512439a6e17bb91 Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:08:31 -0700 Subject: [PATCH 26/44] updating conflict monitor connectors to convert timestamp instead of generate timestamp --- jikkou/kafka-connectors-values.yaml | 58 ++++++++++++++--------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/jikkou/kafka-connectors-values.yaml b/jikkou/kafka-connectors-values.yaml index 829c554..5ea824d 100644 --- a/jikkou/kafka-connectors-values.yaml +++ b/jikkou/kafka-connectors-values.yaml @@ -151,59 +151,59 @@ apps: # Record Events - topicName: topic.CmStopLinePassageEvent collectionName: CmStopLinePassageEvent - generateTimestamp: true + useTimestamp: true timestampField: eventGeneratedAt - topicName: topic.CmStopLineStopEvent collectionName: CmStopLineStopEvent - generateTimestamp: true + useTimestamp: true timestampField: eventGeneratedAt - topicName: topic.CmSignalStateConflictEvents collectionName: CmSignalStateConflictEvents - generateTimestamp: true + useTimestamp: true timestampField: eventGeneratedAt - topicName: topic.CmIntersectionReferenceAlignmentEvents collectionName: CmIntersectionReferenceAlignmentEvents - generateTimestamp: true + useTimestamp: true timestampField: eventGeneratedAt - topicName: topic.CmSignalGroupAlignmentEvents collectionName: CmSignalGroupAlignmentEvents - generateTimestamp: true + useTimestamp: true timestampField: eventGeneratedAt - topicName: topic.CmConnectionOfTravelEvent collectionName: CmConnectionOfTravelEvent - generateTimestamp: true + useTimestamp: true timestampField: eventGeneratedAt - topicName: topic.CmLaneDirectionOfTravelEvent collectionName: CmLaneDirectionOfTravelEvent - generateTimestamp: true + useTimestamp: true timestampField: eventGeneratedAt - topicName: topic.CmSpatTimeChangeDetailsEvent collectionName: CmSpatTimeChangeDetailsEvent - generateTimestamp: true + useTimestamp: true timestampField: eventGeneratedAt - topicName: topic.CmSpatMinimumDataEvents collectionName: CmSpatMinimumDataEvents - generateTimestamp: true + useTimestamp: true timestampField: eventGeneratedAt - topicName: topic.CmMapBroadcastRateEvents collectionName: CmMapBroadcastRateEvents - generateTimestamp: true + useTimestamp: true timestampField: eventGeneratedAt - topicName: topic.CmMapMinimumDataEvents collectionName: CmMapMinimumDataEvents - generateTimestamp: true + useTimestamp: true timestampField: eventGeneratedAt - topicName: topic.CmSpatBroadcastRateEvents collectionName: CmSpatBroadcastRateEvents - generateTimestamp: true + useTimestamp: true timestampField: eventGeneratedAt - topicName: topic.CmTimestampDeltaEvent collectionName: CmTimestampDeltaEvent - generateTimestamp: true + useTimestamp: true timestampField: eventGeneratedAt - topicName: topic.CmEventStateProgressionEvent collectionName: CmEventStateProgressionEvent - generateTimestamp: true + useTimestamp: true timestampField: eventGeneratedAt # Record BSM events: @@ -214,71 +214,71 @@ apps: # Record Assessments: - topicName: topic.CmLaneDirectionOfTravelAssessment collectionName: CmLaneDirectionOfTravelAssessment - generateTimestamp: true + useTimestamp: true timestampField: assessmentGeneratedAt - topicName: topic.CmConnectionOfTravelAssessment collectionName: CmConnectionOfTravelAssessment - generateTimestamp: true + useTimestamp: true timestampField: assessmentGeneratedAt - topicName: topic.CmSignalStateEventAssessment collectionName: CmSignalStateEventAssessment - generateTimestamp: true + useTimestamp: true timestampField: assessmentGeneratedAt - topicName: topic.CmStopLineStopAssessment collectionName: CmStopLineStopAssessment - generateTimestamp: true + useTimestamp: true timestampField: assessmentGeneratedAt # Record Notifications - topicName: topic.CmSpatTimeChangeDetailsNotification collectionName: CmSpatTimeChangeDetailsNotification - generateTimestamp: true + useTimestamp: true timestampField: notificationGeneratedAt - topicName: topic.CmLaneDirectionOfTravelNotification collectionName: CmLaneDirectionOfTravelNotification - generateTimestamp: true + useTimestamp: true timestampField: notificationGeneratedAt - topicName: topic.CmConnectionOfTravelNotification collectionName: CmConnectionOfTravelNotification - generateTimestamp: true + useTimestamp: true timestampField: notificationGeneratedAt - topicName: topic.CmAppHealthNotifications collectionName: CmAppHealthNotifications - generateTimestamp: true + useTimestamp: true timestampField: notificationGeneratedAt - topicName: topic.CmSignalStateConflictNotification collectionName: CmSignalStateConflictNotification - generateTimestamp: true + useTimestamp: true timestampField: notificationGeneratedAt - topicName: topic.CmSignalGroupAlignmentNotification collectionName: CmSignalGroupAlignmentNotification - generateTimestamp: true + useTimestamp: true timestampField: notificationGeneratedAt - topicName: topic.CmNotification collectionName: CmNotification - generateTimestamp: true + useTimestamp: true timestampField: notificationGeneratedAt useKey: true keyField: key - topicName: topic.CmStopLineStopNotification collectionName: CmStopLineStopNotification - generateTimestamp: true + useTimestamp: true timestampField: notificationGeneratedAt useKey: true keyField: key - topicName: topic.CmStopLinePassageNotification collectionName: CmStopLinePassageNotification - generateTimestamp: true + useTimestamp: true timestampField: notificationGeneratedAt useKey: true keyField: key - topicName: topic.CmTimestampDeltaNotification collectionName: CmTimestampDeltaNotification - generateTimestamp: true + useTimestamp: true timestampField: notificationGeneratedAt useKey: true keyField: key - topicName: topic.CmEventStateProgressionNotification collectionName: CmEventStateProgressionNotification - generateTimestamp: true + useTimestamp: true timestampField: eventGeneratedAt \ No newline at end of file From 3833c09248ef9d23e4ea5659a3af9c256acda721 Mon Sep 17 00:00:00 2001 From: Ivan Yourshaw <39739503+iyourshaw@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:38:03 -0700 Subject: [PATCH 27/44] Connector configs --- kafka/kafka-connectors-values.yaml | 62 +++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/kafka/kafka-connectors-values.yaml b/kafka/kafka-connectors-values.yaml index 829c554..dea887e 100644 --- a/kafka/kafka-connectors-values.yaml +++ b/kafka/kafka-connectors-values.yaml @@ -205,6 +205,46 @@ apps: collectionName: CmEventStateProgressionEvent generateTimestamp: true timestampField: eventGeneratedAt + - topicName: topic.CmSpatMinimumDataEventAggregation + collectionName: CmSpatMinimumDataEventAggregation + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmMapMinimumDataEventAggregation + collectionName: CmMapMinimumDataEventAggregation + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmIntersectionReferenceAlignmentEventAggregation + collectionName: CmIntersectionReferenceAlignmentEventAggregation + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmSignalGroupAlignmentEventAggregation + collectionName: CmSignalGroupAlignmentEventAggregation + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmSignalStateConflictEventAggregation + collectionName: CmSignalStateConflictEventAggregation + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmSpatTimeChangeDetailsEventAggregation + collectionName: CmSpatTimeChangeDetailsEventAggregation + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmEventStateProgressionEventAggregation + collectionName: CmEventStateProgressionEventAggregation + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmBsmMessageCountProgressionEventAggregation + collectionName: CmBsmMessageCountProgressionEventAggregation + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmMapMessageCountProgressionEventAggregation + collectionName: CmMapMessageCountProgressionEventAggregation + generateTimestamp: true + timestampField: eventGeneratedAt + - topicName: topic.CmSpatMessageCountProgressionEventAggregation + collectionName: CmSpatMessageCountProgressionEventAggregation + generateTimestamp: true + timestampField: eventGeneratedAt # Record BSM events: - topicName: topic.CmBsmEvents @@ -281,4 +321,24 @@ apps: - topicName: topic.CmEventStateProgressionNotification collectionName: CmEventStateProgressionNotification generateTimestamp: true - timestampField: eventGeneratedAt \ No newline at end of file + timestampField: eventGeneratedAt + - topicName: topic.CmIntersectionReferenceAlignmentNotificationAggregation + collectionName: CmIntersectionReferenceAlignmentNotificationAggregation + generateTimestamp: true + timestampField: notificationGeneratedAt + - topicName: topic.CmSignalGroupAlignmentNotificationAggregation + collectionName: CmSignalGroupAlignmentNotificationAggregation + generateTimestamp: true + timestampField: notificationGeneratedAt + - topicName: topic.CmSignalStateConflictNotificationAggregation + collectionName: CmSignalStateConflictNotificationAggregation + generateTimestamp: true + timestampField: notificationGeneratedAt + - topicName: topic.CmSpatTimeChangeDetailsNotificationAggregation + collectionName: CmSpatTimeChangeDetailsNotificationAggregation + generateTimestamp: true + timestampField: notificationGeneratedAt + - topicName: topic.CmEventStateProgressionNotificationAggregation + collectionName: CmEventStateProgressionNotificationAggregation + generateTimestamp: true + timestampField: notificationGeneratedAt \ No newline at end of file From 4f8ec12eeff20ef4a6337203378d5b33e7e98aa3 Mon Sep 17 00:00:00 2001 From: Ivan Yourshaw <39739503+iyourshaw@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:54:08 -0700 Subject: [PATCH 28/44] Fix merged file --- jikkou/kafka-connectors-values.yaml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/jikkou/kafka-connectors-values.yaml b/jikkou/kafka-connectors-values.yaml index def726f..534a4ea 100644 --- a/jikkou/kafka-connectors-values.yaml +++ b/jikkou/kafka-connectors-values.yaml @@ -328,10 +328,9 @@ apps: keyField: key - topicName: topic.CmEventStateProgressionNotification collectionName: CmEventStateProgressionNotification -<<<<<<< HEAD:kafka/kafka-connectors-values.yaml generateTimestamp: true timestampField: eventGeneratedAt - - topicName: topic.CmIntersectionReferenceAlignmentNotificationAggregation + - topicName: topic.CmIntersectionReferenceAlignmentNotificationAggregation collectionName: CmIntersectionReferenceAlignmentNotificationAggregation generateTimestamp: true timestampField: notificationGeneratedAt @@ -351,7 +350,4 @@ apps: collectionName: CmEventStateProgressionNotificationAggregation generateTimestamp: true timestampField: notificationGeneratedAt -======= - useTimestamp: true - timestampField: eventGeneratedAt ->>>>>>> develop:jikkou/kafka-connectors-values.yaml + From 4d79b518b4e9ee334ed433e90bfe95263dead7af Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Wed, 11 Dec 2024 14:46:44 -0700 Subject: [PATCH 29/44] chore: make kafka_init.sh executable via git so its copied with exec permissions to image --- jikkou/kafka_init.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 jikkou/kafka_init.sh diff --git a/jikkou/kafka_init.sh b/jikkou/kafka_init.sh old mode 100644 new mode 100755 From 33e874283fd7abb6d6c980e55f635654c5f2f1ba Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Wed, 11 Dec 2024 15:57:29 -0700 Subject: [PATCH 30/44] chore: make kafka_connector_init.sh executable via git so its copied with exec permissions to image --- jikkou/kafka_connector_init.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 jikkou/kafka_connector_init.sh diff --git a/jikkou/kafka_connector_init.sh b/jikkou/kafka_connector_init.sh old mode 100644 new mode 100755 From a84a3e7abccbb9f691a99c441efb4229694b2b71 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Wed, 11 Dec 2024 15:58:32 -0700 Subject: [PATCH 31/44] chore: make setup_mongo.sh executable via git --- mongo/setup_mongo.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 mongo/setup_mongo.sh diff --git a/mongo/setup_mongo.sh b/mongo/setup_mongo.sh old mode 100644 new mode 100755 From 6eae7be322fc02d8bbc80c5940e30fb3d713be36 Mon Sep 17 00:00:00 2001 From: Ivan Yourshaw <39739503+iyourshaw@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:46:09 -0700 Subject: [PATCH 32/44] Add aggregation events/notifications to mongo/create_indexes.js --- mongo/create_indexes.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/mongo/create_indexes.js b/mongo/create_indexes.js index 13d9f1a..96fffe8 100644 --- a/mongo/create_indexes.js +++ b/mongo/create_indexes.js @@ -113,6 +113,17 @@ const conflictMonitorCollections = [ { name: "CmMapMessageCountProgressionEvents", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, { name: "CmBsmMessageCountProgressionEvents", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "SpatMinimumDataAggregation", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "MapMinimumDataAggregation", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "IntersectionReferenceAlignmentAggregation", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "SignalGroupAlignmentAggregation", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "SignalStateConflictAggregation", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "TimeChangeDetailsAggregation", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "EventStateProgressionAggregation", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "BsmMessageCountProgressionAggregation", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "MapMessageCountProgressionAggregation", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "SpatMessageCountProgressionAggregation", ttlField: "eventGeneratedAt", timeField: "eventGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + // Conflict Monitor Assessments { name: "CmLaneDirectionOfTravelAssessment", ttlField: "assessmentGeneratedAt", timeField: "assessmentGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, @@ -129,7 +140,14 @@ const conflictMonitorCollections = [ { name: "CmSignalGroupAlignmentNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, { name: "CmStopLinePassageNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, { name: "CmStopLineStopNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, - { name: "CmNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds } + { name: "CmNotification", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + + { name: "EventStateProgressionNotificationAggregation", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "IntersectionReferenceAlignmentNotificationAggregation", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "SignalGroupAlignmentNotificationAggregation", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "SignalStateConflictNotificationAggregation", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds }, + { name: "TimeChangeDetailsNotificationAggregation", ttlField: "notificationGeneratedAt", timeField: "notificationGeneratedAt", intersectionField: "intersectionID", expireTime: expireSeconds } + ]; let collections = []; From 2295722d82c94eb3c6317291a4fdf07f2310423d Mon Sep 17 00:00:00 2001 From: John-Wiens Date: Fri, 13 Dec 2024 10:58:06 -0700 Subject: [PATCH 33/44] Adding JPO-Deduplicator to jpo-utils --- .github/workflows/ci.yml | 43 ++ .github/workflows/docker.yml | 24 + .github/workflows/dockerhub.yml | 38 ++ docker-compose-deduplicator.yml | 34 ++ docker-compose.yml | 3 +- jpo-deduplicator/.dockerignore | 1 + jpo-deduplicator/Dockerfile | 57 +++ .../jpo-deduplicator/LICENSE-2.0.html | 228 +++++++++ jpo-deduplicator/jpo-deduplicator/pom.xml | 294 ++++++++++++ .../jpo-deduplicator/settings.xml | 54 +++ .../deduplicator/DeduplicatorApplication.java | 57 +++ .../deduplicator/DeduplicatorProperties.java | 444 ++++++++++++++++++ .../jpo/deduplicator/KafkaConfiguration.java | 197 ++++++++ .../deduplicator/StreamsExceptionHandler.java | 56 +++ .../DeduplicatorServiceController.java | 107 +++++ .../deduplicator/models/JsonPair.java | 21 + .../deduplicator/models/OdeBsmPair.java | 20 + .../deduplicator/models/OdeMapPair.java | 20 + .../deduplicator/models/Pair.java | 19 + .../deduplicator/models/ProcessedMapPair.java | 21 + .../models/ProcessedMapWktPair.java | 20 + .../processors/DeduplicationProcessor.java | 69 +++ .../processors/OdeBsmJsonProcessor.java | 80 ++++ .../processors/OdeMapJsonProcessor.java | 70 +++ .../OdeRawEncodedTimJsonProcessor.java | 43 ++ .../processors/OdeTimJsonProcessor.java | 42 ++ .../processors/ProcessedMapProcessor.java | 57 +++ .../processors/ProcessedMapWktProcessor.java | 55 +++ .../processors/ProcessedSpatProcessor.java | 64 +++ .../OdeBsmJsonProcessorSupplier.java | 26 + .../OdeMapJsonProcessorSupplier.java | 22 + .../OdeRawEncodedTimProcessorSupplier.java | 22 + .../OdeTimJsonProcessorSupplier.java | 22 + .../ProcessedMapProcessorSupplier.java | 23 + .../ProcessedMapWktProcessorSupplier.java | 22 + .../ProcessedSpatProcessorSupplier.java | 22 + .../serialization/JsonSerdes.java | 24 + .../serialization/PairSerdes.java | 52 ++ .../topologies/BsmDeduplicatorTopology.java | 125 +++++ .../topologies/MapDeduplicatorTopology.java | 121 +++++ .../OdeRawEncodedTimDeduplicatorTopology.java | 131 ++++++ .../ProcessedMapDeduplicatorTopology.java | 92 ++++ .../ProcessedMapWktDeduplicatorTopology.java | 93 ++++ .../ProcessedSpatDeduplicatorTopology.java | 97 ++++ .../topologies/TimDeduplicatorTopology.java | 126 +++++ .../src/main/resources/application.yaml | 96 ++++ .../src/main/resources/logback.xml | 20 + .../BsmDeduplicatorTopologyTest.java | 116 +++++ .../MapDeduplicatorTopologyTest.java | 96 ++++ ...RawEncodedTimDeduplicatorTopologyTest.java | 89 ++++ .../ProcessedMapDeduplicatorTopologyTest.java | 99 ++++ ...ocessedMapWktDeduplicatorTopologyTest.java | 101 ++++ ...ProcessedSpatDeduplicatorTopologyTest.java | 106 +++++ .../TimDeduplicatorTopologyTest.java | 89 ++++ .../resources/application-testConfig.yaml | 1 + .../src/test/resources/logback-test.xml | 11 + .../classes/META-INF/build-info.properties | 5 + sample.env | 11 +- 58 files changed, 4096 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/docker.yml create mode 100644 .github/workflows/dockerhub.yml create mode 100644 docker-compose-deduplicator.yml create mode 100644 jpo-deduplicator/.dockerignore create mode 100644 jpo-deduplicator/Dockerfile create mode 100644 jpo-deduplicator/jpo-deduplicator/LICENSE-2.0.html create mode 100644 jpo-deduplicator/jpo-deduplicator/pom.xml create mode 100644 jpo-deduplicator/jpo-deduplicator/settings.xml create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/DeduplicatorApplication.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/DeduplicatorProperties.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/KafkaConfiguration.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/StreamsExceptionHandler.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/DeduplicatorServiceController.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/JsonPair.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/OdeBsmPair.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/OdeMapPair.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/Pair.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/ProcessedMapPair.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/ProcessedMapWktPair.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/DeduplicationProcessor.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeBsmJsonProcessor.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeMapJsonProcessor.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeRawEncodedTimJsonProcessor.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeTimJsonProcessor.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/ProcessedMapProcessor.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/ProcessedMapWktProcessor.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/ProcessedSpatProcessor.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeBsmJsonProcessorSupplier.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeMapJsonProcessorSupplier.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeRawEncodedTimProcessorSupplier.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeTimJsonProcessorSupplier.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/ProcessedMapProcessorSupplier.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/ProcessedMapWktProcessorSupplier.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/ProcessedSpatProcessorSupplier.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/serialization/JsonSerdes.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/serialization/PairSerdes.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/BsmDeduplicatorTopology.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/MapDeduplicatorTopology.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/OdeRawEncodedTimDeduplicatorTopology.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/ProcessedMapDeduplicatorTopology.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/ProcessedMapWktDeduplicatorTopology.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/ProcessedSpatDeduplicatorTopology.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/TimDeduplicatorTopology.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/resources/application.yaml create mode 100644 jpo-deduplicator/jpo-deduplicator/src/main/resources/logback.xml create mode 100644 jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/BsmDeduplicatorTopologyTest.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/MapDeduplicatorTopologyTest.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/OdeRawEncodedTimDeduplicatorTopologyTest.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/ProcessedMapDeduplicatorTopologyTest.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/ProcessedMapWktDeduplicatorTopologyTest.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/ProcessedSpatDeduplicatorTopologyTest.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/TimDeduplicatorTopologyTest.java create mode 100644 jpo-deduplicator/jpo-deduplicator/src/test/resources/application-testConfig.yaml create mode 100644 jpo-deduplicator/jpo-deduplicator/src/test/resources/logback-test.xml create mode 100644 jpo-deduplicator/jpo-deduplicator/target/classes/META-INF/build-info.properties diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d2e8cd0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: CI +on: + pull_request: + types: [opened, reopened, synchronize] + push: + branches: [develop, master] + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Build + uses: docker/build-push-action@v3 + with: + build-args: | + MAVEN_GITHUB_TOKEN_NAME=${{ vars.MAVEN_GITHUB_TOKEN_NAME }} + MAVEN_GITHUB_TOKEN=${{ secrets.MAVEN_GITHUB_TOKEN }} + MAVEN_GITHUB_ORG=${{ github.repository_owner }} + secrets: | + MAVEN_GITHUB_TOKEN: ${{ secrets.MAVEN_GITHUB_TOKEN }} + + sonar: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: "21" + distribution: "temurin" + - name: Run Sonar + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + MAVEN_GITHUB_TOKEN_NAME: ${{ vars.MAVEN_GITHUB_TOKEN_NAME }} + MAVEN_GITHUB_TOKEN: ${{ secrets.MAVEN_GITHUB_TOKEN }} + MAVEN_GITHUB_ORG: ${{ github.repository_owner }} + run: | + cd jpo-deduplicator/jpo-deduplicator + mvn -s settings.xml -e -X clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar -Dsonar.projectKey=usdot-jpo-ode_jpo-deduplicator -Dsonar.projectName=jpo-conflictmonitor -Dsonar.organization=usdot-jpo-ode -Dsonar.host.url=https://sonarcloud.io -Dsonar.branch.name=$GITHUB_REF_NAME diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..fb020e7 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,24 @@ +name: Docker build + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + jpo-deduplicator: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Build + uses: docker/build-push-action@v3 + with: + context: jpo-deduplicator + build-args: | + MAVEN_GITHUB_TOKEN_NAME=${{ vars.MAVEN_GITHUB_TOKEN_NAME }} + MAVEN_GITHUB_TOKEN=${{ secrets.MAVEN_GITHUB_TOKEN }} + MAVEN_GITHUB_ORG=${{ github.repository_owner }} + secrets: | + MAVEN_GITHUB_TOKEN: ${{ secrets.MAVEN_GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml new file mode 100644 index 0000000..b7d3c85 --- /dev/null +++ b/.github/workflows/dockerhub.yml @@ -0,0 +1,38 @@ +name: "DockerHub Build and Push" + +on: + push: + branches: + - "develop" + - "master" + - "release/*" +jobs: + dockerhub-jpo-deduplicator: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + context: jpo-deduplicator + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Replcae Docker tag + id: set_tag + run: echo "TAG=$(echo ${GITHUB_REF##*/} | sed 's/\//-/g')" >> $GITHUB_ENV + + - name: Build + uses: docker/build-push-action@v3 + with: + push: true + tags: usdotjpoode/jpo-deduplicator:${{ env.TAG }} + build-args: | + MAVEN_GITHUB_TOKEN_NAME=${{ vars.MAVEN_GITHUB_TOKEN_NAME }} + MAVEN_GITHUB_TOKEN=${{ secrets.MAVEN_GITHUB_TOKEN }} + MAVEN_GITHUB_ORG=${{ github.repository_owner }} + secrets: | + MAVEN_GITHUB_TOKEN: ${{ secrets.MAVEN_GITHUB_TOKEN }} \ No newline at end of file diff --git a/docker-compose-deduplicator.yml b/docker-compose-deduplicator.yml new file mode 100644 index 0000000..6b3a021 --- /dev/null +++ b/docker-compose-deduplicator.yml @@ -0,0 +1,34 @@ +services: + deduplicator: + profiles: + - all + - deduplicator + build: + context: jpo-deduplicator + dockerfile: Dockerfile + args: + MAVEN_GITHUB_TOKEN: ${MAVEN_GITHUB_TOKEN:?error} + MAVEN_GITHUB_ORG: ${MAVEN_GITHUB_ORG:?error} + image: jpo-deduplicator:latest + restart: ${RESTART_POLICY} + environment: + DOCKER_HOST_IP: ${DOCKER_HOST_IP} + KAFKA_BOOTSTRAP_SERVERS: ${KAFKA_BOOTSTRAP_SERVERS:?error} + spring.kafka.bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:?error} + healthcheck: + test: ["CMD", "java", "-version"] + interval: 10s + timeout: 10s + retries: 20 + logging: + options: + max-size: "10m" + max-file: "5" + deploy: + resources: + limits: + memory: 3G + depends_on: + kafka: + condition: service_healthy + required: false diff --git a/docker-compose.yml b/docker-compose.yml index 4410b10..c0719e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,5 @@ include: - docker-compose-connect.yml - docker-compose-mongo.yml - - docker-compose-kafka.yml \ No newline at end of file + - docker-compose-kafka.yml + - docker-compose-deduplicator.yml \ No newline at end of file diff --git a/jpo-deduplicator/.dockerignore b/jpo-deduplicator/.dockerignore new file mode 100644 index 0000000..1ace176 --- /dev/null +++ b/jpo-deduplicator/.dockerignore @@ -0,0 +1 @@ +jpo-deduplicator/target/ \ No newline at end of file diff --git a/jpo-deduplicator/Dockerfile b/jpo-deduplicator/Dockerfile new file mode 100644 index 0000000..c3df17e --- /dev/null +++ b/jpo-deduplicator/Dockerfile @@ -0,0 +1,57 @@ +FROM maven:3.8-eclipse-temurin-21-alpine AS builder + +WORKDIR /home + +ARG MAVEN_GITHUB_TOKEN +ARG MAVEN_GITHUB_ORG + +ENV MAVEN_GITHUB_TOKEN=$MAVEN_GITHUB_TOKEN +ENV MAVEN_GITHUB_ORG=$MAVEN_GITHUB_ORG + +# COPY ./jpo-conflictmonitor/pom.xml ./jpo-conflictmonitor/ +# COPY ./settings.xml ./jpo-conflictmonitor/ + +# # Copy and Build Conflict Monitor +# # Download dependencies alone to cache them first +# WORKDIR /home/jpo-conflictmonitor +# RUN mvn -s settings.xml dependency:resolve + +# # Copy the source code and build the conflict monitor +# COPY ./jpo-conflictmonitor/src ./src +# RUN mvn -s settings.xml install -DskipTests -Ppackage-jar + +# Copy and Build Deduplicator +WORKDIR /home +COPY ./jpo-deduplicator/pom.xml ./jpo-deduplicator/ +COPY ./settings.xml ./jpo-deduplicator/ + +WORKDIR /home/jpo-deduplicator +RUN mvn -s settings.xml dependency:resolve + +COPY ./jpo-deduplicator/src ./src +RUN mvn -s settings.xml install -DskipTests + +FROM amazoncorretto:21 + +WORKDIR /home + +COPY --from=builder /home/jpo-deduplicator/src/main/resources/application.yaml /home +COPY --from=builder /home/jpo-deduplicator/src/main/resources/logback.xml /home +COPY --from=builder /home/jpo-deduplicator/target/jpo-deduplicator.jar /home + +#COPY cert.crt /home/cert.crt +#RUN keytool -import -trustcacerts -keystore /usr/local/openjdk-11/lib/security/cacerts -storepass changeit -noprompt -alias mycert -file cert.crt + +ENTRYPOINT ["java", \ + "-Djava.rmi.server.hostname=$DOCKER_HOST_IP", \ + "-Dcom.sun.management.jmxremote.port=9090", \ + "-Dcom.sun.management.jmxremote.rmi.port=9090", \ + "-Dcom.sun.management.jmxremote", \ + "-Dcom.sun.management.jmxremote.local.only=true", \ + "-Dcom.sun.management.jmxremote.authenticate=false", \ + "-Dcom.sun.management.jmxremote.ssl=false", \ + "-Dlogback.configurationFile=/home/logback.xml", \ + "-jar", \ + "/home/jpo-deduplicator.jar"] + +# ENTRYPOINT ["tail", "-f", "/dev/null"] diff --git a/jpo-deduplicator/jpo-deduplicator/LICENSE-2.0.html b/jpo-deduplicator/jpo-deduplicator/LICENSE-2.0.html new file mode 100644 index 0000000..d1055c8 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/LICENSE-2.0.html @@ -0,0 +1,228 @@ + + + + + + Apache License, Version 2.0 + + +
+ +

Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/

+

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND + DISTRIBUTION

+

1. + Definitions.

+

"License" shall mean the terms and conditions for use, + reproduction, and distribution as defined by Sections 1 through 9 + of this document.

+

"Licensor" shall mean the copyright owner or entity authorized + by the copyright owner that is granting the License.

+

"Legal Entity" shall mean the union of the acting entity and + all other entities that control, are controlled by, or are under + common control with that entity. For the purposes of this + definition, "control" means (i) the power, direct or indirect, to + cause the direction or management of such entity, whether by + contract or otherwise, or (ii) ownership of fifty percent (50%) + or more of the outstanding shares, or (iii) beneficial ownership + of such entity.

+

"You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License.

+

"Source" form shall mean the preferred form for making + modifications, including but not limited to software source code, + documentation source, and configuration files.

+

"Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but not + limited to compiled object code, generated documentation, and + conversions to other media types.

+

"Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work (an + example is provided in the Appendix below).

+

"Derivative Works" shall mean any work, whether in Source or + Object form, that is based on (or derived from) the Work and for + which the editorial revisions, annotations, elaborations, or + other modifications represent, as a whole, an original work of + authorship. For the purposes of this License, Derivative Works + shall not include works that remain separable from, or merely + link (or bind by name) to the interfaces of, the Work and + Derivative Works thereof.

+

"Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or + additions to that Work or Derivative Works thereof, that is + intentionally submitted to Licensor for inclusion in the Work by + the copyright owner or by an individual or Legal Entity + authorized to submit on behalf of the copyright owner. For the + purposes of this definition, "submitted" means any form of + electronic, verbal, or written communication sent to the Licensor + or its representatives, including but not limited to + communication on electronic mailing lists, source code control + systems, and issue tracking systems that are managed by, or on + behalf of, the Licensor for the purpose of discussing and + improving the Work, but excluding communication that is + conspicuously marked or otherwise designated in writing by the + copyright owner as "Not a Contribution."

+

"Contributor" shall mean Licensor and any individual or Legal + Entity on behalf of whom a Contribution has been received by + Licensor and subsequently incorporated within the Work.

+

2. Grant of + Copyright License. Subject to the terms and + conditions of this License, each Contributor hereby grants to You + a perpetual, worldwide, non-exclusive, no-charge, royalty-free, + irrevocable copyright license to reproduce, prepare Derivative + Works of, publicly display, publicly perform, sublicense, and + distribute the Work and such Derivative Works in Source or Object + form.

+

3. Grant of Patent + License. Subject to the terms and conditions of this + License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer + the Work, where such license applies only to those patent claims + licensable by such Contributor that are necessarily infringed by + their Contribution(s) alone or by combination of their + Contribution(s) with the Work to which such Contribution(s) was + submitted. If You institute patent litigation against any entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that the Work or a Contribution incorporated within the Work + constitutes direct or contributory patent infringement, then any + patent licenses granted to You under this License for that Work + shall terminate as of the date such litigation is filed.

+

4. + Redistribution. You may reproduce and distribute + copies of the Work or Derivative Works thereof in any medium, + with or without modifications, and in Source or Object form, + provided that You meet the following conditions:

+
    +
  1. You must give any other recipients of the Work or + Derivative Works a copy of this License; and
  2. +
  3. You must cause any modified files to carry prominent + notices stating that You changed the files; and
  4. +
  5. You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, excluding + those notices that do not pertain to any part of the Derivative + Works; and
  6. +
  7. If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute + must include a readable copy of the attribution notices + contained within such NOTICE file, excluding those notices that + do not pertain to any part of the Derivative Works, in at least + one of the following places: within a NOTICE text file + distributed as part of the Derivative Works; within the Source + form or documentation, if provided along with the Derivative + Works; or, within a display generated by the Derivative Works, + if and wherever such third-party notices normally appear. The + contents of the NOTICE file are for informational purposes only + and do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed as + modifying the License.
    +
    + You may add Your own copyright statement to Your modifications + and may provide additional or different license terms and + conditions for use, reproduction, or distribution of Your + modifications, or for any such Derivative Works as a whole, + provided Your use, reproduction, and distribution of the Work + otherwise complies with the conditions stated in this + License.
  8. +
+

5. + Submission of Contributions. Unless You explicitly + state otherwise, any Contribution intentionally submitted for + inclusion in the Work by You to the Licensor shall be under the + terms and conditions of this License, without any additional + terms or conditions. Notwithstanding the above, nothing herein + shall supersede or modify the terms of any separate license + agreement you may have executed with Licensor regarding such + Contributions.

+

6. + Trademarks. This License does not grant permission + to use the trade names, trademarks, service marks, or product + names of the Licensor, except as required for reasonable and + customary use in describing the origin of the Work and + reproducing the content of the NOTICE file.

+

7. Disclaimer + of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or + conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or + FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for + determining the appropriateness of using or redistributing the + Work and assume any risks associated with Your exercise of + permissions under this License.

+

8. Limitation + of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, + special, incidental, or consequential damages of any character + arising as a result of this License or out of the use or + inability to use the Work (including but not limited to damages + for loss of goodwill, work stoppage, computer failure or + malfunction, or any and all other commercial damages or losses), + even if such Contributor has been advised of the possibility of + such damages.

+

9. Accepting + Warranty or Additional Liability. While + redistributing the Work or Derivative Works thereof, You may + choose to offer, and charge a fee for, acceptance of support, + warranty, indemnity, or other liability obligations and/or rights + consistent with this License. However, in accepting such + obligations, You may act only on Your own behalf and on Your sole + responsibility, not on behalf of any other Contributor, and only + if You agree to indemnify, defend, and hold each Contributor + harmless for any liability incurred by, or claims asserted + against, such Contributor by reason of your accepting any such + warranty or additional liability.

+

END OF TERMS AND CONDITIONS

+

APPENDIX: How to apply the Apache License to your + work

+

To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a file + or class name and description of purpose be included on the same + "printed page" as the copyright notice for easier identification + within third-party archives.

+
+
Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+    
+
+
+ + + diff --git a/jpo-deduplicator/jpo-deduplicator/pom.xml b/jpo-deduplicator/jpo-deduplicator/pom.xml new file mode 100644 index 0000000..3a358e2 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/pom.xml @@ -0,0 +1,294 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.3 + + + usdot.jpo.ode + jpo-deduplicator + 1.3.1 + jar + jpo-deduplicator + http://maven.apache.org + + UTF-8 + 29-SNAPSHOT + usdot-jpo-ode + https://sonarcloud.io + + Deduplicates Messages generated by Subcomponents of the ODE + 21 + + ^ + + + 0.8.11 + jacoco + reuseReports + ${project.basedir}/target/site/jacoco/jacoco.xml + java + + + + + org.springframework.boot + spring-boot-starter + + + org.apache.logging.log4j + log4j-to-slf4j + + + + + org.springframework.boot + spring-boot-starter-test + test + + + com.vaadin.external.google + android-json + + + + + + org.springframework.boot + spring-boot-devtools + runtime + + + org.springframework.boot + spring-boot-starter-data-rest + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.restdocs + spring-restdocs-mockmvc + test + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-websocket + + + org.springframework.kafka + spring-kafka + + + org.springframework.kafka + spring-kafka-test + test + + + + + usdot.jpo.ode + jpo-ode-core + 3.0.0 + + + usdot.jpo.ode + jpo-ode-plugins + 3.0.0 + + + usdot.jpo.ode + jpo-geojsonconverter + 1.4.2 + + + usdot.jpo.ode + jpo-conflictmonitor + 1.5.0 + + + junit + junit + 4.13.2 + test + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + test + + + org.mockito + mockito-core + 5.10.0 + test + + + + com.google.guava + guava + 33.0.0-jre + + + + + + + + org.projectlombok + lombok + 1.18.30 + + + + + + + + + false + + central + Central Repository + https://repo.maven.apache.org/maven2 + + + + + + ${project.artifactId} + + + org.sonarsource.scanner.maven + sonar-maven-plugin + 3.10.0.2594 + + + org.springframework.boot + spring-boot-maven-plugin + + @{argLine} + + + + build-info + + build-info + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + all + exit + + + + @{argLine} + -XX:+EnableDynamicAgentLoading + + + + + + org.apache.maven.surefire + surefire-junit47 + 3.2.5 + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + jacoco-initialize + + prepare-agent + + + + jacoco-site + package + + report + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + jar + + package + + + jpo-deduplicator + + + + + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 3.2.5 + + + + diff --git a/jpo-deduplicator/jpo-deduplicator/settings.xml b/jpo-deduplicator/jpo-deduplicator/settings.xml new file mode 100644 index 0000000..eaee228 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/settings.xml @@ -0,0 +1,54 @@ + + + + default + + + + github + jpo_deduplicator + ${env.MAVEN_GITHUB_TOKEN} + + + github_jpo_ode + jpo_deduplicator + ${env.MAVEN_GITHUB_TOKEN} + + + github_jpo_geojsonconverter + jpo_deduplicator + ${env.MAVEN_GITHUB_TOKEN} + + + + + default + + + github + GitHub JPO Conflict Monitor + https://maven.pkg.github.com/${env.MAVEN_GITHUB_ORG}/jpo-conflictmonitor + + false + + + + github_jpo_ode + GitHub JPO ODE + https://maven.pkg.github.com/${env.MAVEN_GITHUB_ORG}/jpo-ode + + false + + + + github_jpo_geojsonconverter + GitHub JPO GeojsonConverter + https://maven.pkg.github.com/${env.MAVEN_GITHUB_ORG}/jpo-geojsonconverter + + false + + + + + + \ No newline at end of file diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/DeduplicatorApplication.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/DeduplicatorApplication.java new file mode 100644 index 0000000..6b483c9 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/DeduplicatorApplication.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright 2018 572682 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + ******************************************************************************/ +package us.dot.its.jpo.deduplicator; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.MBeanRegistrationException; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +@EnableConfigurationProperties(DeduplicatorProperties.class) +public class DeduplicatorApplication { + + private static final Logger logger = LoggerFactory.getLogger(DeduplicatorApplication.class); + + static final int DEFAULT_NO_THREADS = 10; + static final String DEFAULT_SCHEMA = "default"; + + public static void main(String[] args) throws MalformedObjectNameException, InterruptedException, + InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { + + logger.info("Starting Message Deduplicator"); + SpringApplication.run(DeduplicatorApplication.class, args); + // MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + // SystemConfig mBean = new SystemConfig(DEFAULT_NO_THREADS, DEFAULT_SCHEMA); + // ObjectName name = new ObjectName("us.dot.its.jpo.geojson:type=SystemConfig"); + // mbs.registerMBean(mBean, name); + logger.info("Message Deduplicator has started"); + + } + + @Bean + CommandLineRunner init(DeduplicatorProperties geojsonProperties) { + return args -> { + }; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/DeduplicatorProperties.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/DeduplicatorProperties.java new file mode 100644 index 0000000..c60f522 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/DeduplicatorProperties.java @@ -0,0 +1,444 @@ +/******************************************************************************* + * Copyright 2018 572682 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + ******************************************************************************/ +package us.dot.its.jpo.deduplicator; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Properties; +import java.util.UUID; + +import jakarta.annotation.PostConstruct; + +import org.apache.commons.lang3.SystemUtils; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.StreamsConfig; +import org.apache.kafka.streams.errors.LogAndContinueExceptionHandler; +import org.apache.kafka.streams.processor.LogAndSkipOnInvalidTimestamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.info.BuildProperties; +import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.Environment; + +import lombok.Getter; +import lombok.Setter; +import lombok.AccessLevel; +import us.dot.its.jpo.conflictmonitor.AlwaysContinueProductionExceptionHandler; +import us.dot.its.jpo.ode.eventlog.EventLogger; +import us.dot.its.jpo.ode.util.CommonUtils; + +// import us.dot.its.jpo.conflictmonitor.AlwaysContinueProductionExceptionHandler; + +@Getter +@Setter +@ConfigurationProperties +public class DeduplicatorProperties implements EnvironmentAware { + + private static final Logger logger = LoggerFactory.getLogger(DeduplicatorProperties.class); + + // Processed Map Configuration + private String kafkaTopicProcessedMap; + private String kafkaTopicDeduplicatedProcessedMap; + private boolean enableProcessedMapDeduplication; + private String kafkaStateStoreProcessedMapName = "ProcessedMap-store"; + + // Processed Map WKT Configuration + private String kafkaTopicProcessedMapWKT; + private String kafkaTopicDeduplicatedProcessedMapWKT; + private boolean enableProcessedMapWktDeduplication; + private String kafkaStateStoreProcessedMapWKTName = "ProcessedMapWKT-store"; + + // Ode Map Json Configuration + private String kafkaTopicOdeMapJson; + private String kafkaTopicDeduplicatedOdeMapJson; + private boolean enableOdeMapDeduplication; + private String kafkaStateStoreOdeMapJsonName = "OdeMapJson-store"; + + // Ode Tim Json Configuration + private String kafkaTopicOdeTimJson; + private String kafkaTopicDeduplicatedOdeTimJson; + private boolean enableOdeTimDeduplication; + private String kafkaStateStoreOdeTimJsonName = "OdeTimJson-store"; + + // Ode Raw Encoded Tim Json Configuration + private String kafkaTopicOdeRawEncodedTimJson; + private String kafkaTopicDeduplicatedOdeRawEncodedTimJson; + private boolean enableOdeRawEncodedTimDeduplication; + private String kafkaStateStoreOdeRawEncodedTimJsonName = "OdeRawEncodedTimJson-store"; + + //Ode BsmJson Configuration + private String kafkaTopicOdeBsmJson; + private String kafkaTopicDeduplicatedOdeBsmJson; + private boolean enableOdeBsmDeduplication; + private long odeBsmMaximumTimeDelta; + private double odeBsmMaximumPositionDelta; + private double odeBsmAlwaysIncludeAtSpeed; + private String kafkaStateStoreOdeBsmJsonName = "OdeBsmJson-store"; + + // Confluent Properties + private boolean confluentCloudEnabled = false; + private String confluentKey = null; + private String confluentSecret = null; + + // Processed SPaT Configuration + private String kafkaTopicProcessedSpat; + private String kafkaTopicDeduplicatedProcessedSpat; + private boolean enableProcessedSpatDeduplication; + private String kafkaStateStoreProcessedSpatName = "ProcessedSpat-store"; + + + private int lingerMs = 0; + + + + @Autowired + @Setter(AccessLevel.NONE) + private Environment env; + + /* + * General Properties + */ + private String version; + // public static final int OUTPUT_SCHEMA_VERSION = 6; + + @Setter(AccessLevel.NONE) + private String kafkaBrokers = null; + + private static final String DEFAULT_KAFKA_PORT = "9092"; + private static final String DEFAULT_CONNECT_PORT = "8083"; + + @Setter(AccessLevel.NONE) + private String hostId; + + // @Setter(AccessLevel.NONE) + // private String connectURL = null; + + // @Setter(AccessLevel.NONE) + // private String dockerHostIP = null; + + @Setter(AccessLevel.NONE) + private String kafkaBrokerIP = null; + + + // @Setter(AccessLevel.NONE) + // private String dbHostIP = null; + + + @Setter(AccessLevel.NONE) + @Autowired + BuildProperties buildProperties; + + @PostConstruct + void initialize() { + setVersion(buildProperties.getVersion()); + logger.info("groupId: {}", buildProperties.getGroup()); + logger.info("artifactId: {}", buildProperties.getArtifact()); + logger.info("version: {}", version); + //OdeMsgMetadata.setStaticSchemaVersion(OUTPUT_SCHEMA_VERSION); + + + + String hostname; + try { + hostname = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + // Let's just use a random hostname + hostname = UUID.randomUUID().toString(); + logger.info("Unknown host error: {}, using random", e); + } + hostId = hostname; + logger.info("Host ID: {}", hostId); + EventLogger.logger.info("Initializing services on host {}", hostId); + + // if(dbHostIP == null){ + // String dbHost = CommonUtils.getEnvironmentVariable("MONGO_IP"); + + // if(dbHost == null){ + // logger.warn( + // "DB Host IP not defined, Defaulting to localhost."); + // dbHost = "localhost"; + // } + // dbHostIP = dbHost; + // } + + if (kafkaBrokers == null) { + + String kafkaBrokers = CommonUtils.getEnvironmentVariable("KAFKA_BOOTSTRAP_SERVERS"); + + logger.info("ode.kafkaBrokers property not defined. Will try KAFKA_BOOTSTRAP_SERVERS => {}", kafkaBrokers); + + if (kafkaBrokers == null) { + logger.warn( + "Neither ode.kafkaBrokers ode property nor KAFKA_BOOTSTRAP_SERVERS environment variable are defined. Defaulting to localhost:9092"); + kafkaBrokers = "localhost:9092"; + } + + } + + String kafkaType = CommonUtils.getEnvironmentVariable("KAFKA_TYPE"); + if (kafkaType != null) { + confluentCloudEnabled = kafkaType.equals("CONFLUENT"); + if (confluentCloudEnabled) { + + System.out.println("Enabling Confluent Cloud Integration"); + + confluentKey = CommonUtils.getEnvironmentVariable("CONFLUENT_KEY"); + confluentSecret = CommonUtils.getEnvironmentVariable("CONFLUENT_SECRET"); + } + } + + // Initialize the Kafka Connect URL + // if (connectURL == null) { + // String connectURL = CommonUtils.getEnvironmentVariable("CONNECT_URL"); + // if (connectURL == null) { + // connectURL = String.format("http://%s:%s", "localhost", DEFAULT_CONNECT_PORT); + // } + // } + + // List asList = Arrays.asList(this.getKafkaTopicsDisabled()); + // logger.info("Disabled Topics: {}", asList); + // kafkaTopicsDisabledSet.addAll(asList); + } + + public Properties createStreamProperties(String name) { + Properties streamProps = new Properties(); + streamProps.put(StreamsConfig.APPLICATION_ID_CONFIG, name); + + streamProps.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaBrokers); + + streamProps.put(StreamsConfig.DEFAULT_DESERIALIZATION_EXCEPTION_HANDLER_CLASS_CONFIG, + LogAndContinueExceptionHandler.class.getName()); + + streamProps.put(StreamsConfig.DEFAULT_TIMESTAMP_EXTRACTOR_CLASS_CONFIG, + LogAndSkipOnInvalidTimestamp.class.getName()); + + streamProps.put(StreamsConfig.DEFAULT_PRODUCTION_EXCEPTION_HANDLER_CLASS_CONFIG, + AlwaysContinueProductionExceptionHandler.class.getName()); + + streamProps.put(StreamsConfig.NUM_STREAM_THREADS_CONFIG, 2); + + // streamProps.put(StreamsConfig.producerPrefix("acks"), "all"); + streamProps.put(StreamsConfig.producerPrefix(ProducerConfig.ACKS_CONFIG), "all"); + + // Reduce cache buffering per topology to 1MB + streamProps.put(StreamsConfig.STATESTORE_CACHE_MAX_BYTES_CONFIG, 1 * 1024 * 1024L); + + // Decrease default commit interval. Default for 'at least once' mode of 30000ms + // is too slow. + streamProps.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, 100); + + // All the keys are Strings in this app + streamProps.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName()); + + // Configure the state store location + if (SystemUtils.IS_OS_LINUX) { + streamProps.put(StreamsConfig.STATE_DIR_CONFIG, "/var/lib/ode/kafka-streams"); + } else if (SystemUtils.IS_OS_WINDOWS) { + streamProps.put(StreamsConfig.STATE_DIR_CONFIG, "C:/temp/ode"); + } + // streamProps.put(StreamsConfig.STATE_DIR_CONFIG, "/var/lib/")\ + + // Increase max.block.ms and delivery.timeout.ms for streams + final int FIVE_MINUTES_MS = 5 * 60 * 1000; + streamProps.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, FIVE_MINUTES_MS); + streamProps.put(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, FIVE_MINUTES_MS); + + // Disable batching + // streamProps.put(ProducerConfig.BATCH_SIZE_CONFIG, 0); + + streamProps.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "zstd"); + streamProps.put(ProducerConfig.LINGER_MS_CONFIG, getKafkaLingerMs()); + + if (confluentCloudEnabled) { + streamProps.put("ssl.endpoint.identification.algorithm", "https"); + streamProps.put("security.protocol", "SASL_SSL"); + streamProps.put("sasl.mechanism", "PLAIN"); + + if (confluentKey != null && confluentSecret != null) { + String auth = "org.apache.kafka.common.security.plain.PlainLoginModule required " + + "username=\"" + confluentKey + "\" " + + "password=\"" + confluentSecret + "\";"; + streamProps.put("sasl.jaas.config", auth); + } + else { + logger.error("Environment variables CONFLUENT_KEY and CONFLUENT_SECRET are not set. Set these in the .env file to use Confluent Cloud"); + } + } + + + return streamProps; + } + + public String getProperty(String key) { + return env.getProperty(key); + } + + public String getProperty(String key, String defaultValue) { + return env.getProperty(key, defaultValue); + } + + public Object getProperty(String key, int i) { + return env.getProperty(key, Integer.class, i); + } + + public Boolean getConfluentCloudStatus() { + return confluentCloudEnabled; + } + + + @Value("${kafkaTopicProcessedMap}") + public void setKafkaTopicProcessedMap(String kafkaTopicProcessedMap) { + this.kafkaTopicProcessedMap = kafkaTopicProcessedMap; + } + + @Value("${kafkaTopicDeduplicatedProcessedMap}") + public void setKafkaTopicDeduplicatedProcessedMap(String kafkaTopicDeduplicatedProcessedMap) { + this.kafkaTopicDeduplicatedProcessedMap = kafkaTopicDeduplicatedProcessedMap; + } + + @Value("${enableProcessedMapDeduplication}") + public void setEnableProcessedMapDeduplication(boolean enableProcessedMapDeduplication) { + this.enableProcessedMapDeduplication = enableProcessedMapDeduplication; + } + + @Value("${kafkaTopicProcessedMapWKT}") + public void setKafkaTopicProcessedMapWKT(String kafkaTopicProcessedMapWKT) { + this.kafkaTopicProcessedMapWKT = kafkaTopicProcessedMapWKT; + } + + @Value("${kafkaTopicDeduplicatedProcessedMapWKT}") + public void setKafkaTopicDeduplicatedProcessedMapWKT(String kafkaTopicDeduplicatedProcessedMapWKT) { + this.kafkaTopicDeduplicatedProcessedMapWKT = kafkaTopicDeduplicatedProcessedMapWKT; + } + + @Value("${enableProcessedMapWktDeduplication}") + public void setEnableProcessedMapWktDeduplication(boolean enableProcessedMapWktDeduplication) { + this.enableProcessedMapWktDeduplication = enableProcessedMapWktDeduplication; + } + + @Value("${kafkaTopicOdeMapJson}") + public void setKafkaTopicOdeMapJson(String kafkaTopicOdeMapJson) { + this.kafkaTopicOdeMapJson = kafkaTopicOdeMapJson; + } + + @Value("${kafkaTopicDeduplicatedOdeMapJson}") + public void setKafkaTopicDeduplicatedOdeMapJson(String kafkaTopicDeduplicatedOdeMapJson) { + this.kafkaTopicDeduplicatedOdeMapJson = kafkaTopicDeduplicatedOdeMapJson; + } + + @Value("${enableOdeMapDeduplication}") + public void setEnableOdeMapDeduplication(boolean enableOdeMapDeduplication) { + this.enableOdeMapDeduplication = enableOdeMapDeduplication; + } + + @Value("${kafkaTopicOdeTimJson}") + public void setKafkaTopicOdeTimJson(String kafkaTopicOdeTimJson) { + this.kafkaTopicOdeTimJson = kafkaTopicOdeTimJson; + } + + @Value("${kafkaTopicDeduplicatedOdeTimJson}") + public void setKafkaTopicDeduplicatedOdeTimJson(String kafkaTopicDeduplicatedOdeTimJson) { + this.kafkaTopicDeduplicatedOdeTimJson = kafkaTopicDeduplicatedOdeTimJson; + } + + @Value("${enableOdeTimDeduplication}") + public void setEnableOdeTimDeduplication(boolean enableOdeTimDeduplication) { + this.enableOdeTimDeduplication = enableOdeTimDeduplication; + } + + @Value("${kafkaTopicOdeRawEncodedTimJson}") + public void setKafkaTopicOdeRawEncodedTimJson(String kafkaTopicOdeRawEncodedTimJson) { + this.kafkaTopicOdeRawEncodedTimJson = kafkaTopicOdeRawEncodedTimJson; + } + + @Value("${kafkaTopicDeduplicatedOdeRawEncodedTimJson}") + public void setKafkaTopicDeduplicatedOdeRawEncodedTimJson(String kafkaTopicDeduplicatedOdeRawEncodedTimJson) { + this.kafkaTopicDeduplicatedOdeRawEncodedTimJson = kafkaTopicDeduplicatedOdeRawEncodedTimJson; + } + + @Value("${enableOdeRawEncodedTimDeduplication}") + public void setEnableOdeRawEncodedTimDeduplication(boolean enableOdeRawEncodedTimDeduplication) { + this.enableOdeRawEncodedTimDeduplication = enableOdeRawEncodedTimDeduplication; + } + + @Value("${kafkaTopicOdeBsmJson}") + public void setkafkaTopicOdeBsmJson(String kafkaTopicOdeBsmJson) { + this.kafkaTopicOdeBsmJson = kafkaTopicOdeBsmJson; + } + + @Value("${kafkaTopicDeduplicatedOdeBsmJson}") + public void setkafkaTopicDeduplicatedOdeBsmJson(String kafkaTopicDeduplicatedOdeBsmJson) { + this.kafkaTopicDeduplicatedOdeBsmJson = kafkaTopicDeduplicatedOdeBsmJson; + } + + @Value("${enableOdeBsmDeduplication}") + public void setenableOdeBsmDeduplication(boolean enableOdeBsmDeduplication) { + this.enableOdeBsmDeduplication = enableOdeBsmDeduplication; + } + + @Value("${odeBsmMaximumTimeDelta}") + public void setMaximumTimeDelta(long maximumTimeDelta) { + this.odeBsmMaximumTimeDelta = maximumTimeDelta; + } + + @Value("${odeBsmMaximumPositionDelta}") + public void setMaximumPositionDelta(double maximumPositionDelta) { + this.odeBsmMaximumPositionDelta = maximumPositionDelta; + } + + @Value("${odeBsmAlwaysIncludeAtSpeed}") + public void setAlwaysIncludeAtSpeed(double alwaysIncludeAtSpeed) { + this.odeBsmAlwaysIncludeAtSpeed = alwaysIncludeAtSpeed; + } + + @Value("${spring.kafka.bootstrap-servers}") + public void setKafkaBrokers(String kafkaBrokers) { + this.kafkaBrokers = kafkaBrokers; + } + + @Value("${kafkaTopicProcessedSpat}") + public void setKafkaTopicProcessedSpat(String kafkaTopicProcessedSpat) { + this.kafkaTopicProcessedSpat = kafkaTopicProcessedSpat; + } + + @Value("${kafkaTopicDeduplicatedProcessedSpat}") + public void setKafkaTopicDeduplicatedProcessedSpat(String kafkaTopicDeduplicatedProcessedSpat) { + this.kafkaTopicDeduplicatedProcessedSpat = kafkaTopicDeduplicatedProcessedSpat; + } + + @Value("${enableProcessedSpatDeduplication}") + public void setEnableProcessedSpatDeduplication(boolean enableProcessedSpatDeduplication) { + this.enableProcessedSpatDeduplication = enableProcessedSpatDeduplication; + } + + @Override + public void setEnvironment(Environment environment) { + env = environment; + } + + @Value("${kafka.linger_ms}") + public void setKafkaLingerMs(int lingerMs) { + this.lingerMs = lingerMs; + } + + public int getKafkaLingerMs() { + return lingerMs; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/KafkaConfiguration.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/KafkaConfiguration.java new file mode 100644 index 0000000..a5cac5f --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/KafkaConfiguration.java @@ -0,0 +1,197 @@ +package us.dot.its.jpo.deduplicator; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import org.apache.kafka.clients.admin.NewTopic; +import org.apache.kafka.common.serialization.StringSerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.kafka.config.TopicBuilder; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaAdmin; +import org.springframework.kafka.core.KafkaAdmin.NewTopics; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.core.ProducerFactory; +import org.springframework.stereotype.Component; +import org.apache.kafka.clients.producer.ProducerConfig; + +@Component +@ConfigurationProperties(prefix = "kafka.topics") +public class KafkaConfiguration { + + @Autowired + private DeduplicatorProperties properties; + + @Bean(name = "createKafkaTopics") + public KafkaAdmin.NewTopics createKafkaTopics() { + logger.info("createTopic"); + List newTopics = new ArrayList<>(); + + if(!properties.getConfluentCloudStatus()){ + try { + for(var propEntry : admin.getConfigurationProperties().entrySet()) { + logger.info("KafkaAdmin property {} = {}", propEntry.getKey(), propEntry.getValue()); + } + + if (!autoCreateTopics) { + logger.info("Auto create topics is disabled"); + return null; + } + + logger.info("Creating topics: {}", createTopics); + + + + List topicNames = new ArrayList<>(); + for (var topic : createTopics) { + + // Get the name and config settings for the topic + String topicName = (String)topic.getOrDefault("name", null); + if (topicName == null) { + logger.error("CreateTopic {} has no topic name", topic); + break; + } + topicNames.add(topicName); + + Map topicConfigs = new HashMap<>(); + String cleanupPolicy = (String)topic.getOrDefault("cleanupPolicy", null); + if (cleanupPolicy != null) { + topicConfigs.put("cleanup.policy", cleanupPolicy); + } + Integer retentionMs = (Integer)topic.getOrDefault("retentionMs", null); + if (retentionMs != null) { + topicConfigs.put("retention.ms", retentionMs.toString()); + } + + NewTopic newTopic = TopicBuilder + .name(topicName) + .partitions(numPartitions) + .replicas(numReplicas) + .configs(topicConfigs) + .build(); + newTopics.add(newTopic); + logger.info("New Topic: {}", newTopic); + } + + // Explicitly create the topics here to prevent error on first run + admin.initialize(); + admin.createOrModifyTopics(newTopics.toArray(new NewTopic[0])); + + // Check that topics were created + var topicDescMap = admin.describeTopics(topicNames.toArray(new String[0])); + for (var entry : topicDescMap.entrySet()) { + String topicName = entry.getKey(); + var desc = entry.getValue(); + logger.info("Created topic {}: {}", topicName, desc); + } + + + } catch (Exception e) { + logger.error("Exception in createKafkaTopics", e); + throw e; + } + } + + // List out existing topics + // Admin adminClient = Admin.create(properties.createStreamProperties("DeduplicatorAdminClient")); + // ListTopicsOptions listTopicsOptions = new ListTopicsOptions().listInternal(true); + // ListTopicsResult topicsResult = adminClient.listTopics(listTopicsOptions); + // KafkaFuture> topicsFuture = topicsResult.names(); + // try { + // List topicNames = new ArrayList<>(); + // for(String topicName: topicsFuture.get()){ + // logger.info("Found Topic: " + topicName); + // topicNames.add(topicName); + // } + + // } catch (InterruptedException e) { + // logger.error("Interruption Exception in createKafkaTopics. Unable to List existing Topics", e); + // } catch (ExecutionException e) { + // logger.error("Execution Exception in createKafkaTopics. Unable to List existing Topics", e); + // } + + + return new NewTopics(newTopics.toArray(NewTopic[]::new)); + } + + @Bean + public ProducerFactory producerFactory() { + Properties configProps = properties.createStreamProperties("Deduplicator-producer-factory"); + + Map map = new HashMap<>(); + + for (Map.Entry entry : configProps.entrySet()) { + String key = (String) entry.getKey(); + Object value = entry.getValue(); + map.put(key, value); + } + + map.put( + ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, + StringSerializer.class); + map.put( + ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, + StringSerializer.class); + + return new DefaultKafkaProducerFactory(map); + } + + @Bean + public KafkaTemplate kafkaTemplate() { + return new KafkaTemplate<>(producerFactory()); + } + + private static final Logger logger = LoggerFactory.getLogger(KafkaConfiguration.class); + + private boolean autoCreateTopics; + private int numPartitions; + private int numReplicas; + private List> createTopics; + + public boolean getAutoCreateTopics() { + return autoCreateTopics; + } + + public void setAutoCreateTopics(boolean autoCreateTopics) { + this.autoCreateTopics = autoCreateTopics; + } + + public int getNumPartitions() { + return numPartitions; + } + + public int getNumReplicas() { + return numReplicas; + } + + public List> getCreateTopics() { + return createTopics; + } + + public void setNumPartitions(int numPartitions) { + this.numPartitions = numPartitions; + logger.info("kafka.topics.numPartitions = {}", numPartitions); + } + + public void setNumReplicas(int numReplicas) { + this.numReplicas = numReplicas; + } + + public void setCreateTopics(List> createTopics) { + this.createTopics = createTopics; + } + + + + @Autowired + private KafkaAdmin admin; + + + +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/StreamsExceptionHandler.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/StreamsExceptionHandler.java new file mode 100644 index 0000000..71132f3 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/StreamsExceptionHandler.java @@ -0,0 +1,56 @@ +package us.dot.its.jpo.deduplicator; + +import org.apache.kafka.streams.errors.StreamsUncaughtExceptionHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.kafka.core.KafkaTemplate; + + + +/** + * Handler for unhandled exceptions thrown from Streams topologies that + * logs the exception to a topic, and allows choosing the shutdown behavior. + * + * See {@link https://cwiki.apache.org/confluence/display/KAFKA/KIP-671%3A+Introduce+Kafka+Streams+Specific+Uncaught+Exception+Handler} + * for a description of the options. + */ +public class StreamsExceptionHandler implements StreamsUncaughtExceptionHandler { + + final static Logger logger = LoggerFactory.getLogger(StreamsExceptionHandler.class); + + final KafkaTemplate kafkaTemplate; + final String topology; + final String notificationTopic; + + @Autowired + public StreamsExceptionHandler(KafkaTemplate kafkaTemplate, String topology, String notificationTopic) { + this.kafkaTemplate = kafkaTemplate; + this.topology = topology; + this.notificationTopic = notificationTopic; + } + + @Override + public StreamThreadExceptionResponse handle(Throwable exception) { + try { + logger.error(String.format("Uncaught exception in stream topology %s", topology), exception); + } catch (Exception ex) { + logger.error("Exception sending kafka streams error event", ex); + } + + + // SHUTDOWN_CLIENT option shuts down quickly. + return StreamThreadExceptionResponse.SHUTDOWN_CLIENT; + + // SHUTDOWN_APPLICATION shuts down more slowly, but cleans up more thoroughly + //return StreamThreadExceptionResponse.SHUTDOWN_APPLICATION; + + // "Replace Thread" mode can be used to keep the streams client alive, + // however if the cause of the error was not transient, but due to a code error processing + // a record, it can result in the record being repeatedly processed throwing the + // same error + // + //return StreamThreadExceptionResponse.REPLACE_THREAD; + } + +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/DeduplicatorServiceController.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/DeduplicatorServiceController.java new file mode 100644 index 0000000..89b44da --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/DeduplicatorServiceController.java @@ -0,0 +1,107 @@ +package us.dot.its.jpo.deduplicator.deduplicator; + +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.kafka.streams.KafkaStreams; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.DependsOn; +import org.springframework.context.annotation.Profile; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Controller; + +import lombok.Getter; +import us.dot.its.jpo.conflictmonitor.monitor.MonitorServiceController; +import us.dot.its.jpo.conflictmonitor.monitor.algorithms.StreamsTopology; +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.topologies.BsmDeduplicatorTopology; +import us.dot.its.jpo.deduplicator.deduplicator.topologies.MapDeduplicatorTopology; +import us.dot.its.jpo.deduplicator.deduplicator.topologies.TimDeduplicatorTopology; +import us.dot.its.jpo.deduplicator.deduplicator.topologies.OdeRawEncodedTimDeduplicatorTopology; +import us.dot.its.jpo.deduplicator.deduplicator.topologies.ProcessedMapDeduplicatorTopology; +import us.dot.its.jpo.deduplicator.deduplicator.topologies.ProcessedMapWktDeduplicatorTopology; +import us.dot.its.jpo.deduplicator.deduplicator.topologies.ProcessedSpatDeduplicatorTopology; + +@Controller +@DependsOn("createKafkaTopics") +@Profile("!test && !testConfig") +public class DeduplicatorServiceController { + + private static final Logger logger = LoggerFactory.getLogger(MonitorServiceController.class); + + // Temporary for KafkaStreams that don't implement the Algorithm interface + @Getter + final ConcurrentHashMap streamsMap = new ConcurrentHashMap(); + + @Getter + final ConcurrentHashMap algoMap = new ConcurrentHashMap(); + + + + @Autowired + public DeduplicatorServiceController(final DeduplicatorProperties props, + final KafkaTemplate kafkaTemplate) { + + + try { + + if(props.isEnableProcessedMapDeduplication()){ + ProcessedMapDeduplicatorTopology processedMapDeduplicatorTopology = new ProcessedMapDeduplicatorTopology( + props, + props.createStreamProperties("ProcessedMapDeduplicator") + ); + processedMapDeduplicatorTopology.start(); + } + + if(props.isEnableProcessedMapWktDeduplication()){ + ProcessedMapWktDeduplicatorTopology processedMapWktDeduplicatorTopology = new ProcessedMapWktDeduplicatorTopology( + props, + props.createStreamProperties("ProcessedMapWKTdeduplicator") + ); + processedMapWktDeduplicatorTopology.start(); + } + + if(props.isEnableProcessedMapDeduplication()){ + MapDeduplicatorTopology mapDeduplicatorTopology = new MapDeduplicatorTopology( + props, + props.createStreamProperties("MapDeduplicator") + ); + mapDeduplicatorTopology.start(); + } + + if(props.isEnableOdeTimDeduplication()){ + TimDeduplicatorTopology timDeduplicatorTopology = new TimDeduplicatorTopology( + props, + props.createStreamProperties("TimDeduplicator") + ); + timDeduplicatorTopology.start(); + } + + if(props.isEnableOdeRawEncodedTimDeduplication()){ + OdeRawEncodedTimDeduplicatorTopology odeRawEncodedTimDeduplicatorTopology = new OdeRawEncodedTimDeduplicatorTopology( + props, + props.createStreamProperties("OdeRawEncodedTimDeduplicator") + ); + odeRawEncodedTimDeduplicatorTopology.start(); + } + + if(props.isEnableProcessedSpatDeduplication()){ + ProcessedSpatDeduplicatorTopology processedSpatDeduplicatorTopology = new ProcessedSpatDeduplicatorTopology( + props, + props.createStreamProperties("ProcessedSpatDeduplicator") + ); + processedSpatDeduplicatorTopology.start(); + } + + if(props.isEnableOdeBsmDeduplication()){ + BsmDeduplicatorTopology bsmDeduplicatorTopology = new BsmDeduplicatorTopology(props); + bsmDeduplicatorTopology.start(); + } + + + } catch (Exception e) { + logger.error("Encountered issue with creating topologies", e); + } + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/JsonPair.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/JsonPair.java new file mode 100644 index 0000000..d0f4377 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/JsonPair.java @@ -0,0 +1,21 @@ +package us.dot.its.jpo.deduplicator.deduplicator.models; + +import com.fasterxml.jackson.databind.JsonNode; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@Setter +@Getter +public class JsonPair { + + public JsonNode message; + public boolean shouldSend; + + public JsonPair(JsonNode message, boolean shouldSend){ + this.message = message; + this.shouldSend = shouldSend; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/OdeBsmPair.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/OdeBsmPair.java new file mode 100644 index 0000000..208622b --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/OdeBsmPair.java @@ -0,0 +1,20 @@ +package us.dot.its.jpo.deduplicator.deduplicator.models; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import us.dot.its.jpo.ode.model.OdeBsmData; + +@NoArgsConstructor +@Setter +@Getter +public class OdeBsmPair { + + public OdeBsmData message; + public boolean shouldSend; + + public OdeBsmPair(OdeBsmData message, boolean shouldSend){ + this.message = message; + this.shouldSend = shouldSend; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/OdeMapPair.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/OdeMapPair.java new file mode 100644 index 0000000..24c265a --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/OdeMapPair.java @@ -0,0 +1,20 @@ +package us.dot.its.jpo.deduplicator.deduplicator.models; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import us.dot.its.jpo.ode.model.OdeMapData; + +@NoArgsConstructor +@Setter +@Getter +public class OdeMapPair { + + public OdeMapData message; + public boolean shouldSend; + + public OdeMapPair(OdeMapData message, boolean shouldSend){ + this.message = message; + this.shouldSend = shouldSend; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/Pair.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/Pair.java new file mode 100644 index 0000000..fd0ee44 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/Pair.java @@ -0,0 +1,19 @@ +package us.dot.its.jpo.deduplicator.deduplicator.models; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@Setter +@Getter +public class Pair { + + public T message; + public boolean shouldSend; + + public Pair(T message, boolean shouldSend){ + this.message = message; + this.shouldSend = shouldSend; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/ProcessedMapPair.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/ProcessedMapPair.java new file mode 100644 index 0000000..b1400c5 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/ProcessedMapPair.java @@ -0,0 +1,21 @@ +package us.dot.its.jpo.deduplicator.deduplicator.models; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.LineString; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.map.ProcessedMap; + +@NoArgsConstructor +@Setter +@Getter +public class ProcessedMapPair { + + public ProcessedMap message; + public boolean shouldSend; + + public ProcessedMapPair(ProcessedMap message, boolean shouldSend){ + this.message = message; + this.shouldSend = shouldSend; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/ProcessedMapWktPair.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/ProcessedMapWktPair.java new file mode 100644 index 0000000..d6acaca --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/models/ProcessedMapWktPair.java @@ -0,0 +1,20 @@ +package us.dot.its.jpo.deduplicator.deduplicator.models; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.map.ProcessedMap; + +@NoArgsConstructor +@Setter +@Getter +public class ProcessedMapWktPair { + + public ProcessedMap message; + public boolean shouldSend; + + public ProcessedMapWktPair(ProcessedMap message, boolean shouldSend){ + this.message = message; + this.shouldSend = shouldSend; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/DeduplicationProcessor.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/DeduplicationProcessor.java new file mode 100644 index 0000000..55b669c --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/DeduplicationProcessor.java @@ -0,0 +1,69 @@ +package us.dot.its.jpo.deduplicator.deduplicator.processors; + +import java.time.Duration; +import java.time.Instant; + +import org.apache.kafka.streams.processor.PunctuationType; +import org.apache.kafka.streams.processor.api.Processor; +import org.apache.kafka.streams.processor.api.ProcessorContext; +import org.apache.kafka.streams.processor.api.Record; +import org.apache.kafka.streams.state.KeyValueStore; + +import org.apache.kafka.streams.state.KeyValueIterator; +import org.apache.kafka.streams.KeyValue; + +public abstract class DeduplicationProcessor implements Processor{ + + private ProcessorContext context; + private KeyValueStore store; + public String storeName; + + @Override + public void init(ProcessorContext context) { + this.context = context; + store = context.getStateStore(storeName); + this.context.schedule(Duration.ofHours(1), PunctuationType.WALL_CLOCK_TIME, this::cleanupOldKeys); + } + + @Override + public void process(Record record) { + + // Don't do anything if key is bad + if(record.key().equals("")){ + return; + } + + T lastRecord = store.get(record.key()); + if(lastRecord == null){ + store.put(record.key(), record.value()); + context.forward(record); + return; + } + + if(!isDuplicate(lastRecord, record.value())){ + store.put(record.key(), record.value()); + context.forward(record); + return; + } + } + + private void cleanupOldKeys(final long timestamp) { + try (KeyValueIterator iterator = store.all()) { + while (iterator.hasNext()) { + + KeyValue record = iterator.next(); + // Delete any record more than 2 hours old. + if(Instant.ofEpochMilli(timestamp).minusSeconds(2 * 60 * 60).isAfter(getMessageTime(record.value))){ + store.delete(record.key); + } + } + } + } + + // returns an instant representing the time of the message + public abstract Instant getMessageTime(T message); + + // returns if two messages are duplicates of one another + public abstract boolean isDuplicate(T lastMessage, T newMessage); + +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeBsmJsonProcessor.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeBsmJsonProcessor.java new file mode 100644 index 0000000..2197b23 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeBsmJsonProcessor.java @@ -0,0 +1,80 @@ +package us.dot.its.jpo.deduplicator.deduplicator.processors; + + +import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; + +import org.geotools.referencing.GeodeticCalculator; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.ode.model.OdeBsmData; +import us.dot.its.jpo.ode.model.OdeBsmMetadata; +import us.dot.its.jpo.ode.plugin.j2735.J2735Bsm; +import us.dot.its.jpo.ode.plugin.j2735.J2735BsmCoreData; + +public class OdeBsmJsonProcessor extends DeduplicationProcessor{ + + DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; + DeduplicatorProperties props; + GeodeticCalculator calculator; + + public OdeBsmJsonProcessor(String storeName, DeduplicatorProperties props){ + this.storeName = storeName; + this.props = props; + calculator = new GeodeticCalculator(); + } + + + @Override + public Instant getMessageTime(OdeBsmData message) { + try { + String time = ((OdeBsmMetadata)message.getMetadata()).getOdeReceivedAt(); + return Instant.from(formatter.parse(time)); + } catch (Exception e) { + System.out.println("Failed to Parse Time"); + return Instant.ofEpochMilli(0); + } + } + + @Override + public boolean isDuplicate(OdeBsmData lastMessage, OdeBsmData newMessage) { + Instant newValueTime = getMessageTime(newMessage); + Instant oldValueTime = getMessageTime(lastMessage); + + // If the messages are more than a certain time apart, forward the new message on + if(newValueTime.minus(Duration.ofMillis(props.getOdeBsmMaximumTimeDelta())).isAfter(oldValueTime)){ + return false; + } + + J2735BsmCoreData oldCore = ((J2735Bsm)lastMessage.getPayload().getData()).getCoreData(); + J2735BsmCoreData newCore = ((J2735Bsm)newMessage.getPayload().getData()).getCoreData(); + + + // If the Vehicle is moving, forward the message on + if(newCore.getSpeed().doubleValue() > props.getOdeBsmAlwaysIncludeAtSpeed()){ + return false; + } + + + double distance = calculateGeodeticDistance( + newCore.getPosition().getLatitude().doubleValue(), + newCore.getPosition().getLongitude().doubleValue(), + oldCore.getPosition().getLatitude().doubleValue(), + oldCore.getPosition().getLongitude().doubleValue() + ); + + // If the position delta between the messages is suitable large, forward the message on + if(distance > props.getOdeBsmMaximumPositionDelta()){ + return false; + } + + return true; + } + + public double calculateGeodeticDistance(double lat1, double lon1, double lat2, double lon2) { + calculator.setStartingGeographicPoint(lon1, lat1); + calculator.setDestinationGeographicPoint(lon2, lat2); + return calculator.getOrthodromicDistance(); + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeMapJsonProcessor.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeMapJsonProcessor.java new file mode 100644 index 0000000..adaf6db --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeMapJsonProcessor.java @@ -0,0 +1,70 @@ +package us.dot.its.jpo.deduplicator.deduplicator.processors; + +import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.ode.model.OdeMapData; +import us.dot.its.jpo.ode.model.OdeMapMetadata; +import us.dot.its.jpo.ode.model.OdeMapPayload; + +public class OdeMapJsonProcessor extends DeduplicationProcessor{ + + DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; + + DeduplicatorProperties props; + + public OdeMapJsonProcessor(DeduplicatorProperties props){ + this.props = props; + this.storeName = props.getKafkaStateStoreOdeMapJsonName(); + } + + + @Override + public Instant getMessageTime(OdeMapData message) { + try { + String time = ((OdeMapMetadata)message.getMetadata()).getOdeReceivedAt(); + return Instant.from(formatter.parse(time)); + } catch (Exception e) { + return Instant.ofEpochMilli(0); + } + } + + @Override + public boolean isDuplicate(OdeMapData lastMessage, OdeMapData newMessage) { + + Instant newValueTime = getMessageTime(newMessage); + Instant oldValueTime = getMessageTime(lastMessage); + + if(newValueTime.minus(Duration.ofHours(1)).isAfter(oldValueTime)){ + return false; + + }else{ + OdeMapPayload oldPayload = (OdeMapPayload)lastMessage.getPayload(); + OdeMapPayload newPayload = (OdeMapPayload)newMessage.getPayload(); + + Integer oldTimestamp = oldPayload.getMap().getTimeStamp(); + Integer newTimestamp = newPayload.getMap().getTimeStamp(); + + + newPayload.getMap().setTimeStamp(oldTimestamp); + + int oldHash = hashMapMessage(lastMessage); + int newhash = hashMapMessage(newMessage); + + if(oldHash != newhash){ + newPayload.getMap().setTimeStamp(newTimestamp); + return false; + } + } + return true; + } + + public int hashMapMessage(OdeMapData map){ + OdeMapPayload payload = (OdeMapPayload)map.getPayload(); + return Objects.hash(payload.toJson()); + + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeRawEncodedTimJsonProcessor.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeRawEncodedTimJsonProcessor.java new file mode 100644 index 0000000..cca49a7 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeRawEncodedTimJsonProcessor.java @@ -0,0 +1,43 @@ +package us.dot.its.jpo.deduplicator.deduplicator.processors; + +import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; + +import com.fasterxml.jackson.databind.JsonNode; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; + +public class OdeRawEncodedTimJsonProcessor extends DeduplicationProcessor{ + + DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; + + DeduplicatorProperties props; + + public OdeRawEncodedTimJsonProcessor(DeduplicatorProperties props){ + this.props = props; + this.storeName = props.getKafkaStateStoreOdeRawEncodedTimJsonName(); + } + + + @Override + public Instant getMessageTime(JsonNode message) { + try { + String time = message.get("metadata").get("odeReceivedAt").asText(); + return Instant.from(formatter.parse(time)); + } catch (Exception e) { + return Instant.ofEpochMilli(0); + } + } + + @Override + public boolean isDuplicate(JsonNode lastMessage, JsonNode newMessage) { + Instant oldValueTime = getMessageTime(lastMessage); + Instant newValueTime = getMessageTime(newMessage); + + if(newValueTime.minus(Duration.ofHours(1)).isAfter(oldValueTime)){ + return false; + } + return true; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeTimJsonProcessor.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeTimJsonProcessor.java new file mode 100644 index 0000000..751ce8c --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/OdeTimJsonProcessor.java @@ -0,0 +1,42 @@ +package us.dot.its.jpo.deduplicator.deduplicator.processors; + +import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; + +import com.fasterxml.jackson.databind.JsonNode; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; + +public class OdeTimJsonProcessor extends DeduplicationProcessor{ + + DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; + + DeduplicatorProperties props; + public OdeTimJsonProcessor(DeduplicatorProperties props){ + this.props = props; + this.storeName = props.getKafkaStateStoreOdeTimJsonName(); + } + + + @Override + public Instant getMessageTime(JsonNode message) { + try { + String time = message.get("metadata").get("odeReceivedAt").asText(); + return Instant.from(formatter.parse(time)); + } catch (Exception e) { + return Instant.ofEpochMilli(0); + } + } + + @Override + public boolean isDuplicate(JsonNode lastMessage, JsonNode newMessage) { + Instant oldValueTime = getMessageTime(lastMessage); + Instant newValueTime = getMessageTime(newMessage); + + if(newValueTime.minus(Duration.ofHours(1)).isAfter(oldValueTime)){ + return false; + } + return true; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/ProcessedMapProcessor.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/ProcessedMapProcessor.java new file mode 100644 index 0000000..35e2c2f --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/ProcessedMapProcessor.java @@ -0,0 +1,57 @@ +package us.dot.its.jpo.deduplicator.deduplicator.processors; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.Objects; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.LineString; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.map.ProcessedMap; + +public class ProcessedMapProcessor extends DeduplicationProcessor>{ + + DeduplicatorProperties props; + + public ProcessedMapProcessor(DeduplicatorProperties props){ + this.props = props; + this.storeName = props.getKafkaStateStoreProcessedMapName(); + } + + + @Override + public Instant getMessageTime(ProcessedMap message) { + try { + return message.getProperties().getOdeReceivedAt().toInstant(); + } catch (Exception e) { + return Instant.ofEpochMilli(0); + } + } + + @Override + public boolean isDuplicate(ProcessedMap lastMessage, ProcessedMap newMessage) { + + Instant newValueTime = getMessageTime(newMessage); + Instant oldValueTime = getMessageTime(lastMessage); + + if(newValueTime.minus(Duration.ofHours(1)).isAfter(oldValueTime)){ + return false; + }else{ + ZonedDateTime newValueTimestamp = newMessage.getProperties().getTimeStamp(); + ZonedDateTime newValueOdeReceivedAt = newMessage.getProperties().getOdeReceivedAt(); + + newMessage.getProperties().setTimeStamp(lastMessage.getProperties().getTimeStamp()); + newMessage.getProperties().setOdeReceivedAt(lastMessage.getProperties().getOdeReceivedAt()); + + int oldHash = Objects.hash(lastMessage.toString()); + int newHash = Objects.hash(newMessage.toString()); + + if(oldHash != newHash){ + newMessage.getProperties().setTimeStamp(newValueTimestamp); + newMessage.getProperties().setOdeReceivedAt(newValueOdeReceivedAt); + return false; + } + } + return true; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/ProcessedMapWktProcessor.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/ProcessedMapWktProcessor.java new file mode 100644 index 0000000..7556060 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/ProcessedMapWktProcessor.java @@ -0,0 +1,55 @@ +package us.dot.its.jpo.deduplicator.deduplicator.processors; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZonedDateTime; +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.map.ProcessedMap; + +public class ProcessedMapWktProcessor extends DeduplicationProcessor>{ + + DeduplicatorProperties props; + + public ProcessedMapWktProcessor(DeduplicatorProperties props){ + this.props = props; + this.storeName = props.getKafkaStateStoreProcessedMapWKTName(); + } + + + @Override + public Instant getMessageTime(ProcessedMap message) { + try { + return message.getProperties().getOdeReceivedAt().toInstant(); + } catch (Exception e) { + return Instant.ofEpochMilli(0); + } + } + + @Override + public boolean isDuplicate(ProcessedMap lastMessage, ProcessedMap newMessage) { + + Instant newValueTime = newMessage.getProperties().getTimeStamp().toInstant(); + Instant oldValueTime = lastMessage.getProperties().getTimeStamp().toInstant(); + + if(newValueTime.minus(Duration.ofHours(1)).isAfter(oldValueTime)){ + return false; + }else{ + ZonedDateTime newValueTimestamp = newMessage.getProperties().getTimeStamp(); + ZonedDateTime newValueOdeReceivedAt = newMessage.getProperties().getOdeReceivedAt(); + + newMessage.getProperties().setTimeStamp(lastMessage.getProperties().getTimeStamp()); + newMessage.getProperties().setOdeReceivedAt(lastMessage.getProperties().getOdeReceivedAt()); + + int oldHash = lastMessage.getProperties().hashCode(); + int newhash = newMessage.getProperties().hashCode(); + + if(oldHash != newhash){ + newMessage.getProperties().setTimeStamp(newValueTimestamp); + newMessage.getProperties().setOdeReceivedAt(newValueOdeReceivedAt); + return false; + } + } + + return true; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/ProcessedSpatProcessor.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/ProcessedSpatProcessor.java new file mode 100644 index 0000000..2cc58af --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/ProcessedSpatProcessor.java @@ -0,0 +1,64 @@ +package us.dot.its.jpo.deduplicator.deduplicator.processors; + + +import java.time.Duration; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.geojsonconverter.pojos.spat.MovementEvent; +import us.dot.its.jpo.geojsonconverter.pojos.spat.MovementState; +import us.dot.its.jpo.geojsonconverter.pojos.spat.ProcessedSpat; + +public class ProcessedSpatProcessor extends DeduplicationProcessor{ + + DeduplicatorProperties props; + + public ProcessedSpatProcessor(DeduplicatorProperties props){ + this.props = props; + this.storeName = props.getKafkaStateStoreProcessedSpatName(); + } + + + @Override + public Instant getMessageTime(ProcessedSpat message) { + return message.getUtcTimeStamp().toInstant(); + } + + @Override + public boolean isDuplicate(ProcessedSpat lastMessage, ProcessedSpat newMessage) { + + Instant newValueTime = getMessageTime(newMessage); + Instant oldValueTime = getMessageTime(lastMessage); + + if(newValueTime.minus(Duration.ofMinutes(1)).isAfter(oldValueTime)){ + return false; + }else{ + HashMap> lastMessageStates = new HashMap<>(); + for(MovementState state: lastMessage.getStates()){ + lastMessageStates.put(state.getSignalGroup(), state.getStateTimeSpeed()); + } + + if(lastMessageStates.size() != newMessage.getStates().size()){ + return false; // message cannot be duplicate if the signal groups have a different number of signal groups + } + + for(MovementState state: newMessage.getStates()){ + List lastMessageState = lastMessageStates.get(state.getSignalGroup()); + + if(lastMessageState == null){ + return false; // messages cannot be duplicates if they have different signal groups + } + + + for(int i=0; i< state.getStateTimeSpeed().size(); i++){ + if(state.getStateTimeSpeed().get(i).getEventState() != lastMessageState.get(i).getEventState()){ + return false; // Some signal group light has changed. Therefore the SPaTs are different + } + } + } + } + return true; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeBsmJsonProcessorSupplier.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeBsmJsonProcessorSupplier.java new file mode 100644 index 0000000..6b08b9f --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeBsmJsonProcessorSupplier.java @@ -0,0 +1,26 @@ +package us.dot.its.jpo.deduplicator.deduplicator.processors.suppliers; + +import java.time.format.DateTimeFormatter; +import org.apache.kafka.streams.processor.api.Processor; +import org.apache.kafka.streams.processor.api.ProcessorSupplier; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.processors.OdeBsmJsonProcessor; +import us.dot.its.jpo.ode.model.OdeBsmData; + +public class OdeBsmJsonProcessorSupplier implements ProcessorSupplier { + + String storeName; + DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; + DeduplicatorProperties props; + + public OdeBsmJsonProcessorSupplier(String storeName, DeduplicatorProperties props){ + this.storeName = storeName; + this.props = props; + } + + @Override + public Processor get() { + return new OdeBsmJsonProcessor(storeName, props); + } +} \ No newline at end of file diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeMapJsonProcessorSupplier.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeMapJsonProcessorSupplier.java new file mode 100644 index 0000000..829af13 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeMapJsonProcessorSupplier.java @@ -0,0 +1,22 @@ +package us.dot.its.jpo.deduplicator.deduplicator.processors.suppliers; + +import org.apache.kafka.streams.processor.api.Processor; +import org.apache.kafka.streams.processor.api.ProcessorSupplier; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.processors.OdeMapJsonProcessor; +import us.dot.its.jpo.ode.model.OdeMapData; + +public class OdeMapJsonProcessorSupplier implements ProcessorSupplier { + + DeduplicatorProperties props; + + public OdeMapJsonProcessorSupplier(DeduplicatorProperties props){ + this.props = props; + } + + @Override + public Processor get() { + return new OdeMapJsonProcessor(props); + } +} \ No newline at end of file diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeRawEncodedTimProcessorSupplier.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeRawEncodedTimProcessorSupplier.java new file mode 100644 index 0000000..ef0ebb7 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeRawEncodedTimProcessorSupplier.java @@ -0,0 +1,22 @@ +package us.dot.its.jpo.deduplicator.deduplicator.processors.suppliers; + +import org.apache.kafka.streams.processor.api.Processor; +import org.apache.kafka.streams.processor.api.ProcessorSupplier; +import com.fasterxml.jackson.databind.JsonNode; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.processors.OdeRawEncodedTimJsonProcessor; + +public class OdeRawEncodedTimProcessorSupplier implements ProcessorSupplier { + + DeduplicatorProperties props; + + public OdeRawEncodedTimProcessorSupplier(DeduplicatorProperties props){ + this.props = props; + } + + @Override + public Processor get() { + return new OdeRawEncodedTimJsonProcessor(props); + } +} \ No newline at end of file diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeTimJsonProcessorSupplier.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeTimJsonProcessorSupplier.java new file mode 100644 index 0000000..7a27588 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/OdeTimJsonProcessorSupplier.java @@ -0,0 +1,22 @@ +package us.dot.its.jpo.deduplicator.deduplicator.processors.suppliers; + +import org.apache.kafka.streams.processor.api.Processor; +import org.apache.kafka.streams.processor.api.ProcessorSupplier; +import com.fasterxml.jackson.databind.JsonNode; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.processors.OdeTimJsonProcessor; + +public class OdeTimJsonProcessorSupplier implements ProcessorSupplier { + + String storeName; + DeduplicatorProperties props; + public OdeTimJsonProcessorSupplier(DeduplicatorProperties props){ + this.props = props; + } + + @Override + public Processor get() { + return new OdeTimJsonProcessor(props); + } +} \ No newline at end of file diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/ProcessedMapProcessorSupplier.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/ProcessedMapProcessorSupplier.java new file mode 100644 index 0000000..9e45cd0 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/ProcessedMapProcessorSupplier.java @@ -0,0 +1,23 @@ +package us.dot.its.jpo.deduplicator.deduplicator.processors.suppliers; + +import org.apache.kafka.streams.processor.api.Processor; +import org.apache.kafka.streams.processor.api.ProcessorSupplier; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.processors.ProcessedMapProcessor; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.LineString; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.map.ProcessedMap; + +public class ProcessedMapProcessorSupplier implements ProcessorSupplier, String, ProcessedMap> { + + DeduplicatorProperties props; + + public ProcessedMapProcessorSupplier(DeduplicatorProperties props){ + this.props = props; + } + + @Override + public Processor, String, ProcessedMap> get() { + return new ProcessedMapProcessor(props); + } +} \ No newline at end of file diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/ProcessedMapWktProcessorSupplier.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/ProcessedMapWktProcessorSupplier.java new file mode 100644 index 0000000..3dbaa8c --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/ProcessedMapWktProcessorSupplier.java @@ -0,0 +1,22 @@ +package us.dot.its.jpo.deduplicator.deduplicator.processors.suppliers; + +import org.apache.kafka.streams.processor.api.Processor; +import org.apache.kafka.streams.processor.api.ProcessorSupplier; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.processors.ProcessedMapWktProcessor; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.map.ProcessedMap; + +public class ProcessedMapWktProcessorSupplier implements ProcessorSupplier, String, ProcessedMap> { + + DeduplicatorProperties props; + + public ProcessedMapWktProcessorSupplier(DeduplicatorProperties props){ + this.props = props; + } + + @Override + public Processor, String, ProcessedMap> get() { + return new ProcessedMapWktProcessor(props); + } +} \ No newline at end of file diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/ProcessedSpatProcessorSupplier.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/ProcessedSpatProcessorSupplier.java new file mode 100644 index 0000000..e6661ac --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/processors/suppliers/ProcessedSpatProcessorSupplier.java @@ -0,0 +1,22 @@ +package us.dot.its.jpo.deduplicator.deduplicator.processors.suppliers; + +import org.apache.kafka.streams.processor.api.Processor; +import org.apache.kafka.streams.processor.api.ProcessorSupplier; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.processors.ProcessedSpatProcessor; +import us.dot.its.jpo.geojsonconverter.pojos.spat.ProcessedSpat; + +public class ProcessedSpatProcessorSupplier implements ProcessorSupplier { + + DeduplicatorProperties props; + + public ProcessedSpatProcessorSupplier(DeduplicatorProperties props){ + this.props = props; + } + + @Override + public Processor get() { + return new ProcessedSpatProcessor(props); + } +} \ No newline at end of file diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/serialization/JsonSerdes.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/serialization/JsonSerdes.java new file mode 100644 index 0000000..8f65168 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/serialization/JsonSerdes.java @@ -0,0 +1,24 @@ +package us.dot.its.jpo.deduplicator.deduplicator.serialization; + +import org.apache.kafka.common.serialization.Serde; +import org.apache.kafka.common.serialization.Serdes; + +import com.fasterxml.jackson.databind.JsonNode; + +import us.dot.its.jpo.geojsonconverter.serialization.deserializers.JsonDeserializer; +import us.dot.its.jpo.geojsonconverter.serialization.serializers.JsonSerializer; +import us.dot.its.jpo.ode.model.OdeTimData; + +public class JsonSerdes { + public static Serde Tim() { + return Serdes.serdeFrom( + new JsonSerializer(), + new JsonDeserializer<>(OdeTimData.class)); + } + + public static Serde JSON(){ + return Serdes.serdeFrom( + new JsonSerializer(), + new JsonDeserializer<>(JsonNode.class)); + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/serialization/PairSerdes.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/serialization/PairSerdes.java new file mode 100644 index 0000000..e509fa0 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/serialization/PairSerdes.java @@ -0,0 +1,52 @@ +package us.dot.its.jpo.deduplicator.deduplicator.serialization; + +import org.apache.kafka.common.serialization.Serde; +import org.apache.kafka.common.serialization.Serdes; + +import us.dot.its.jpo.deduplicator.deduplicator.models.ProcessedMapPair; +import us.dot.its.jpo.deduplicator.deduplicator.models.ProcessedMapWktPair; +import us.dot.its.jpo.deduplicator.deduplicator.models.OdeMapPair; +import us.dot.its.jpo.deduplicator.deduplicator.models.OdeBsmPair; +import us.dot.its.jpo.deduplicator.deduplicator.models.JsonPair; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.map.DeserializedRawMap; +import us.dot.its.jpo.geojsonconverter.serialization.deserializers.JsonDeserializer; +import us.dot.its.jpo.geojsonconverter.serialization.serializers.JsonSerializer; + + +public class PairSerdes { + public static Serde ProcessedMapPair() { + return Serdes.serdeFrom( + new JsonSerializer(), + new JsonDeserializer<>(ProcessedMapPair.class)); + } + + public static Serde ProcessedMapWktPair() { + return Serdes.serdeFrom( + new JsonSerializer(), + new JsonDeserializer<>(ProcessedMapWktPair.class)); + } + + public static Serde OdeMapPair() { + return Serdes.serdeFrom( + new JsonSerializer(), + new JsonDeserializer<>(OdeMapPair.class)); + } + + public static Serde OdeBsmPair() { + return Serdes.serdeFrom( + new JsonSerializer(), + new JsonDeserializer<>(OdeBsmPair.class)); + } + + public static Serde RawMap() { + return Serdes.serdeFrom( + new JsonSerializer(), + new JsonDeserializer<>(DeserializedRawMap.class)); + } + + public static Serde JsonPair() { + return Serdes.serdeFrom( + new JsonSerializer(), + new JsonDeserializer<>(JsonPair.class)); + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/BsmDeduplicatorTopology.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/BsmDeduplicatorTopology.java new file mode 100644 index 0000000..6b26d67 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/BsmDeduplicatorTopology.java @@ -0,0 +1,125 @@ +package us.dot.its.jpo.deduplicator.deduplicator.topologies; + +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.KafkaStreams; +import org.apache.kafka.streams.StreamsBuilder; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.KafkaStreams.StateListener; +import org.apache.kafka.streams.errors.StreamsUncaughtExceptionHandler; + +import us.dot.its.jpo.conflictmonitor.monitor.serialization.JsonSerdes; +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.ode.model.OdeBsmData; +import us.dot.its.jpo.ode.model.OdeBsmMetadata; +import us.dot.its.jpo.ode.model.OdeMapData; +import us.dot.its.jpo.ode.model.OdeMapPayload; +import us.dot.its.jpo.ode.plugin.j2735.J2735Bsm; +import us.dot.its.jpo.ode.plugin.j2735.J2735BsmCoreData; +import org.apache.kafka.streams.kstream.*; +import org.apache.kafka.streams.state.Stores; +import org.geotools.referencing.GeodeticCalculator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +import us.dot.its.jpo.deduplicator.deduplicator.processors.suppliers.OdeBsmJsonProcessorSupplier; +import us.dot.its.jpo.geojsonconverter.DateJsonMapper; + +public class BsmDeduplicatorTopology { + + private static final Logger logger = LoggerFactory.getLogger(BsmDeduplicatorTopology.class); + + Topology topology; + KafkaStreams streams; + DeduplicatorProperties props; + ObjectMapper objectMapper; + DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; + GeodeticCalculator calculator; + + + + public BsmDeduplicatorTopology(DeduplicatorProperties props){ + this.props = props; + this.objectMapper = DateJsonMapper.getInstance(); + calculator = new GeodeticCalculator(); + } + + + + public void start() { + if (streams != null && streams.state().isRunningOrRebalancing()) { + throw new IllegalStateException("Start called while streams is already running."); + } + Topology topology = buildTopology(); + streams = new KafkaStreams(topology, props.createStreamProperties("BsmDeduplicator")); + if (exceptionHandler != null) streams.setUncaughtExceptionHandler(exceptionHandler); + if (stateListener != null) streams.setStateListener(stateListener); + logger.info("Starting Bsm Deduplicator Topology"); + streams.start(); + } + + public Instant getInstantFromBsm(OdeBsmData bsm){ + String time = ((OdeBsmMetadata)bsm.getMetadata()).getOdeReceivedAt(); + + return Instant.from(formatter.parse(time)); + } + + public int hashMapMessage(OdeMapData map){ + OdeMapPayload payload = (OdeMapPayload)map.getPayload(); + return Objects.hash(payload.toJson()); + + } + + public Topology buildTopology() { + StreamsBuilder builder = new StreamsBuilder(); + + KStream inputStream = builder.stream(this.props.getKafkaTopicOdeBsmJson(), Consumed.with(Serdes.Void(), JsonSerdes.OdeBsm())); + + builder.addStateStore(Stores.keyValueStoreBuilder(Stores.persistentKeyValueStore(props.getKafkaStateStoreOdeBsmJsonName()), + Serdes.String(), JsonSerdes.OdeBsm())); + + KStream bsmRekeyedStream = inputStream.selectKey((key, value)->{ + J2735BsmCoreData core = ((J2735Bsm)value.getPayload().getData()).getCoreData(); + return core.getId(); + }).repartition(Repartitioned.with(Serdes.String(), JsonSerdes.OdeBsm())); + + KStream deduplicatedStream = bsmRekeyedStream.process(new OdeBsmJsonProcessorSupplier(props.getKafkaStateStoreOdeBsmJsonName(), props), props.getKafkaStateStoreOdeBsmJsonName()); + + + deduplicatedStream.to(this.props.getKafkaTopicDeduplicatedOdeBsmJson(), Produced.with(Serdes.String(), JsonSerdes.OdeBsm())); + + return builder.build(); + + } + + public void stop() { + logger.info("Stopping Bsm deduplicator Socket Broadcast Topology."); + if (streams != null) { + streams.close(); + streams.cleanUp(); + streams = null; + } + logger.info("Stopped Bsm deduplicator Socket Broadcast Topology."); + } + + StateListener stateListener; + public void registerStateListener(StateListener stateListener) { + this.stateListener = stateListener; + } + + StreamsUncaughtExceptionHandler exceptionHandler; + public void registerUncaughtExceptionHandler(StreamsUncaughtExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } + + public double calculateGeodeticDistance(double lat1, double lon1, double lat2, double lon2) { + calculator.setStartingGeographicPoint(lon1, lat1); + calculator.setDestinationGeographicPoint(lon2, lat2); + return calculator.getOrthodromicDistance(); + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/MapDeduplicatorTopology.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/MapDeduplicatorTopology.java new file mode 100644 index 0000000..c8ac7f6 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/MapDeduplicatorTopology.java @@ -0,0 +1,121 @@ +package us.dot.its.jpo.deduplicator.deduplicator.topologies; + +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.KafkaStreams; +import org.apache.kafka.streams.StreamsBuilder; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.KafkaStreams.StateListener; +import org.apache.kafka.streams.errors.StreamsUncaughtExceptionHandler; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.processors.suppliers.OdeMapJsonProcessorSupplier; +import us.dot.its.jpo.geojsonconverter.DateJsonMapper; +import us.dot.its.jpo.geojsonconverter.partitioner.RsuIntersectionKey; +import us.dot.its.jpo.geojsonconverter.serialization.JsonSerdes; +import us.dot.its.jpo.ode.model.OdeMapData; +import us.dot.its.jpo.ode.model.OdeMapMetadata; +import us.dot.its.jpo.ode.model.OdeMapPayload; +import us.dot.its.jpo.ode.plugin.j2735.J2735IntersectionReferenceID; + +import org.apache.kafka.streams.kstream.*; +import org.apache.kafka.streams.state.Stores; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Objects; +import java.util.Properties; + +public class MapDeduplicatorTopology { + + private static final Logger logger = LoggerFactory.getLogger(MapDeduplicatorTopology.class); + + Topology topology; + KafkaStreams streams; + String inputTopic; + String outputTopic; + Properties streamsProperties; + ObjectMapper objectMapper; + DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; + DeduplicatorProperties props; + + + public MapDeduplicatorTopology(DeduplicatorProperties props, Properties streamsProperties){ + this.props = props; + this.streamsProperties = streamsProperties; + this.objectMapper = DateJsonMapper.getInstance(); + } + + + + public void start() { + if (streams != null && streams.state().isRunningOrRebalancing()) { + throw new IllegalStateException("Start called while streams is already running."); + } + Topology topology = buildTopology(); + streams = new KafkaStreams(topology, streamsProperties); + if (exceptionHandler != null) streams.setUncaughtExceptionHandler(exceptionHandler); + if (stateListener != null) streams.setStateListener(stateListener); + logger.info("Starting Map Deduplicator Topology"); + streams.start(); + } + + public Instant getInstantFromMap(OdeMapData map){ + String time = ((OdeMapMetadata)map.getMetadata()).getOdeReceivedAt(); + + return Instant.from(formatter.parse(time)); + } + + public int hashMapMessage(OdeMapData map){ + OdeMapPayload payload = (OdeMapPayload)map.getPayload(); + return Objects.hash(payload.toJson()); + + } + + public Topology buildTopology() { + StreamsBuilder builder = new StreamsBuilder(); + + KStream inputStream = builder.stream(props.getKafkaTopicOdeMapJson(), Consumed.with(Serdes.Void(), JsonSerdes.OdeMap())); + + builder.addStateStore(Stores.keyValueStoreBuilder(Stores.persistentKeyValueStore(props.getKafkaStateStoreOdeMapJsonName()), + Serdes.String(), JsonSerdes.OdeMap())); + + KStream mapRekeyedStream = inputStream.selectKey((key, value)->{ + J2735IntersectionReferenceID intersectionId = ((OdeMapPayload)value.getPayload()).getMap().getIntersections().getIntersections().get(0).getId(); + RsuIntersectionKey newKey = new RsuIntersectionKey(); + newKey.setRsuId(((OdeMapMetadata)value.getMetadata()).getOriginIp()); + newKey.setIntersectionReferenceID(intersectionId); + return newKey.toString(); + }).repartition(Repartitioned.with(Serdes.String(), JsonSerdes.OdeMap())); + + KStream deduplicatedStream = mapRekeyedStream.process(new OdeMapJsonProcessorSupplier(props), props.getKafkaStateStoreOdeMapJsonName()); + + deduplicatedStream.to(props.getKafkaTopicDeduplicatedOdeMapJson(), Produced.with(Serdes.String(), JsonSerdes.OdeMap())); + + return builder.build(); + + } + + public void stop() { + logger.info("Stopping Map Deduplicator Socket Broadcast Topology."); + if (streams != null) { + streams.close(); + streams.cleanUp(); + streams = null; + } + logger.info("Stopped Map Deduplicator Socket Broadcast Topology."); + } + + StateListener stateListener; + public void registerStateListener(StateListener stateListener) { + this.stateListener = stateListener; + } + + StreamsUncaughtExceptionHandler exceptionHandler; + public void registerUncaughtExceptionHandler(StreamsUncaughtExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/OdeRawEncodedTimDeduplicatorTopology.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/OdeRawEncodedTimDeduplicatorTopology.java new file mode 100644 index 0000000..9431d6b --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/OdeRawEncodedTimDeduplicatorTopology.java @@ -0,0 +1,131 @@ +package us.dot.its.jpo.deduplicator.deduplicator.topologies; + +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.KafkaStreams; +import org.apache.kafka.streams.StreamsBuilder; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.KafkaStreams.StateListener; +import org.apache.kafka.streams.errors.StreamsUncaughtExceptionHandler; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.serialization.JsonSerdes; +import us.dot.its.jpo.geojsonconverter.DateJsonMapper; + +import org.apache.kafka.streams.kstream.*; +import org.apache.kafka.streams.state.Stores; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Objects; +import java.util.Properties; + +import us.dot.its.jpo.deduplicator.deduplicator.processors.suppliers.OdeRawEncodedTimProcessorSupplier; + +public class OdeRawEncodedTimDeduplicatorTopology { + + private static final Logger logger = LoggerFactory.getLogger(OdeRawEncodedTimDeduplicatorTopology.class); + + Topology topology; + KafkaStreams streams; + String inputTopic; + String outputTopic; + Properties streamsProperties; + ObjectMapper objectMapper; + DeduplicatorProperties props; + DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; + + public OdeRawEncodedTimDeduplicatorTopology(DeduplicatorProperties props, Properties streamsProperties){ + this.streamsProperties = streamsProperties; + this.objectMapper = DateJsonMapper.getInstance(); + this.props = props; + } + + + public void start() { + if (streams != null && streams.state().isRunningOrRebalancing()) { + throw new IllegalStateException("Start called while streams is already running."); + } + Topology topology = buildTopology(); + streams = new KafkaStreams(topology, streamsProperties); + if (exceptionHandler != null) streams.setUncaughtExceptionHandler(exceptionHandler); + if (stateListener != null) streams.setStateListener(stateListener); + logger.info("Starting Tim Deduplicator Topology"); + streams.start(); + } + + public JsonNode genJsonNode(){ + return objectMapper.createObjectNode(); + } + + public Instant getInstantFromJsonTim(JsonNode tim){ + try{ + String time = tim.get("metadata").get("odeReceivedAt").asText(); + return Instant.from(formatter.parse(time)); + }catch(Exception e){ + System.out.println("Failed to parse time"); + return Instant.ofEpochMilli(0); + } + } + + + public Topology buildTopology() { + StreamsBuilder builder = new StreamsBuilder(); + + KStream inputStream = builder.stream(props.getKafkaTopicOdeRawEncodedTimJson(), Consumed.with(Serdes.Void(), JsonSerdes.JSON())); + + builder.addStateStore(Stores.keyValueStoreBuilder(Stores.persistentKeyValueStore(props.getKafkaStateStoreOdeRawEncodedTimJsonName()), + Serdes.String(), JsonSerdes.JSON())); + + KStream timRekeyedStream = inputStream.selectKey((key, value)->{ + try{ + + String messageBytes = value.get("payload") + .get("data") + .get("bytes").asText(); + + int hash = Objects.hash(messageBytes); + + String rsuIP = value.get("metadata").get("originIp").asText(); + + String newKey = rsuIP + "_" + hash; + return newKey; + }catch(Exception e){ + return ""; + } + }).repartition(Repartitioned.with(Serdes.String(), JsonSerdes.JSON())); + + + + KStream deduplicatedStream = timRekeyedStream.process(new OdeRawEncodedTimProcessorSupplier(props), props.getKafkaStateStoreOdeRawEncodedTimJsonName()); + + deduplicatedStream.to(props.getKafkaTopicDeduplicatedOdeRawEncodedTimJson(), Produced.with(Serdes.String(), JsonSerdes.JSON())); + + return builder.build(); + + } + + public void stop() { + logger.info("Stopping OdeRawEncodedTim Deduplicator Socket Broadcast Topology."); + if (streams != null) { + streams.close(); + streams.cleanUp(); + streams = null; + } + logger.info("Stopped OdeRawEncodedTim Deduplicator Socket Broadcast Topology."); + } + + StateListener stateListener; + public void registerStateListener(StateListener stateListener) { + this.stateListener = stateListener; + } + + StreamsUncaughtExceptionHandler exceptionHandler; + public void registerUncaughtExceptionHandler(StreamsUncaughtExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/ProcessedMapDeduplicatorTopology.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/ProcessedMapDeduplicatorTopology.java new file mode 100644 index 0000000..5f9da3d --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/ProcessedMapDeduplicatorTopology.java @@ -0,0 +1,92 @@ +package us.dot.its.jpo.deduplicator.deduplicator.topologies; + +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.KafkaStreams; +import org.apache.kafka.streams.StreamsBuilder; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.KafkaStreams.StateListener; +import org.apache.kafka.streams.errors.StreamsUncaughtExceptionHandler; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.processors.suppliers.ProcessedMapProcessorSupplier; +import us.dot.its.jpo.geojsonconverter.DateJsonMapper; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.LineString; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.map.ProcessedMap; +import us.dot.its.jpo.geojsonconverter.serialization.JsonSerdes; +import org.apache.kafka.streams.kstream.*; +import org.apache.kafka.streams.state.Stores; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.databind.ObjectMapper; + + +import java.util.Properties; + +public class ProcessedMapDeduplicatorTopology { + + private static final Logger logger = LoggerFactory.getLogger(MapDeduplicatorTopology.class); + + Topology topology; + KafkaStreams streams; + Properties streamsProperties; + ObjectMapper objectMapper; + DeduplicatorProperties props; + + public ProcessedMapDeduplicatorTopology(DeduplicatorProperties props, Properties streamsProperties){ + this.props = props; + this.streamsProperties = streamsProperties; + this.objectMapper = DateJsonMapper.getInstance(); + } + + + public void start() { + if (streams != null && streams.state().isRunningOrRebalancing()) { + throw new IllegalStateException("Start called while streams is already running."); + } + Topology topology = buildTopology(); + streams = new KafkaStreams(topology, streamsProperties); + if (exceptionHandler != null) streams.setUncaughtExceptionHandler(exceptionHandler); + if (stateListener != null) streams.setStateListener(stateListener); + logger.info("Starting Processed Map Deduplicator Topology"); + streams.start(); + } + + public Topology buildTopology() { + StreamsBuilder builder = new StreamsBuilder(); + + KStream> inputStream = builder.stream(props.getKafkaTopicProcessedMap(), Consumed.with(Serdes.String(), JsonSerdes.ProcessedMapGeoJson())); + + builder.addStateStore(Stores.keyValueStoreBuilder(Stores.persistentKeyValueStore(props.getKafkaStateStoreProcessedMapName()), + Serdes.String(), JsonSerdes.ProcessedMapGeoJson())); + + KStream> deduplicatedStream = inputStream.process(new ProcessedMapProcessorSupplier(props), props.getKafkaStateStoreProcessedMapName()); + + deduplicatedStream.to(props.getKafkaTopicDeduplicatedProcessedMap(), Produced.with(Serdes.String(), JsonSerdes.ProcessedMapGeoJson())); + + return builder.build(); + + } + + public void stop() { + logger.info("Stopping Processed Map deduplicator Socket Broadcast Topology."); + if (streams != null) { + streams.close(); + streams.cleanUp(); + streams = null; + } + logger.info("Stopped Processed Map deduplicator Socket Broadcast Topology."); + } + + StateListener stateListener; + public void registerStateListener(StateListener stateListener) { + this.stateListener = stateListener; + } + + StreamsUncaughtExceptionHandler exceptionHandler; + public void registerUncaughtExceptionHandler(StreamsUncaughtExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } + + + +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/ProcessedMapWktDeduplicatorTopology.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/ProcessedMapWktDeduplicatorTopology.java new file mode 100644 index 0000000..872e66f --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/ProcessedMapWktDeduplicatorTopology.java @@ -0,0 +1,93 @@ +package us.dot.its.jpo.deduplicator.deduplicator.topologies; + +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.KafkaStreams; +import org.apache.kafka.streams.StreamsBuilder; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.KafkaStreams.StateListener; +import org.apache.kafka.streams.errors.StreamsUncaughtExceptionHandler; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.processors.suppliers.ProcessedMapWktProcessorSupplier; +import us.dot.its.jpo.geojsonconverter.DateJsonMapper; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.map.ProcessedMap; +import us.dot.its.jpo.geojsonconverter.serialization.JsonSerdes; + +import org.apache.kafka.streams.kstream.*; +import org.apache.kafka.streams.state.Stores; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.Properties; + +public class ProcessedMapWktDeduplicatorTopology { + + private static final Logger logger = LoggerFactory.getLogger(MapDeduplicatorTopology.class); + + Topology topology; + KafkaStreams streams; + String inputTopic; + String outputTopic; + Properties streamsProperties; + ObjectMapper objectMapper; + DeduplicatorProperties props; + + public ProcessedMapWktDeduplicatorTopology(DeduplicatorProperties props, Properties streamsProperties){ + this.props = props; + this.streamsProperties = streamsProperties; + this.objectMapper = DateJsonMapper.getInstance(); + } + + + public void start() { + if (streams != null && streams.state().isRunningOrRebalancing()) { + throw new IllegalStateException("Start called while streams is already running."); + } + Topology topology = buildTopology(); + streams = new KafkaStreams(topology, streamsProperties); + if (exceptionHandler != null) streams.setUncaughtExceptionHandler(exceptionHandler); + if (stateListener != null) streams.setStateListener(stateListener); + logger.info("Starting Processed Map WKT Deduplicator Topology"); + streams.start(); + } + + public Topology buildTopology() { + StreamsBuilder builder = new StreamsBuilder(); + + KStream> inputStream = builder.stream(props.getKafkaTopicProcessedMapWKT(), Consumed.with(Serdes.String(), JsonSerdes.ProcessedMapWKT())); + + builder.addStateStore(Stores.keyValueStoreBuilder(Stores.persistentKeyValueStore(props.getKafkaStateStoreProcessedMapWKTName()), + Serdes.String(), JsonSerdes.ProcessedMapWKT())); + + KStream> deduplicatedStream = inputStream.process(new ProcessedMapWktProcessorSupplier(props), props.getKafkaStateStoreProcessedMapWKTName()); + + deduplicatedStream.to(props.getKafkaTopicDeduplicatedProcessedMapWKT(), Produced.with(Serdes.String(), JsonSerdes.ProcessedMapWKT())); + + return builder.build(); + + } + + public void stop() { + logger.info("Stopping Processed Map deduplicator Socket Broadcast Topology."); + if (streams != null) { + streams.close(); + streams.cleanUp(); + streams = null; + } + logger.info("Stopped Processed Map deduplicator Socket Broadcast Topology."); + } + + StateListener stateListener; + public void registerStateListener(StateListener stateListener) { + this.stateListener = stateListener; + } + + StreamsUncaughtExceptionHandler exceptionHandler; + public void registerUncaughtExceptionHandler(StreamsUncaughtExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } + + + +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/ProcessedSpatDeduplicatorTopology.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/ProcessedSpatDeduplicatorTopology.java new file mode 100644 index 0000000..b478669 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/ProcessedSpatDeduplicatorTopology.java @@ -0,0 +1,97 @@ +package us.dot.its.jpo.deduplicator.deduplicator.topologies; + +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.KafkaStreams; +import org.apache.kafka.streams.StreamsBuilder; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.KafkaStreams.StateListener; +import org.apache.kafka.streams.errors.StreamsUncaughtExceptionHandler; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.geojsonconverter.DateJsonMapper; +import us.dot.its.jpo.deduplicator.deduplicator.processors.suppliers.ProcessedSpatProcessorSupplier; +import us.dot.its.jpo.geojsonconverter.pojos.spat.ProcessedSpat; +import us.dot.its.jpo.geojsonconverter.serialization.JsonSerdes; + +import org.apache.kafka.streams.kstream.*; +import org.apache.kafka.streams.state.Stores; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.time.format.DateTimeFormatter; +import java.util.Properties; + +public class ProcessedSpatDeduplicatorTopology { + + private static final Logger logger = LoggerFactory.getLogger(ProcessedSpatDeduplicatorTopology.class); + + Topology topology; + KafkaStreams streams; + String inputTopic; + String outputTopic; + Properties streamsProperties; + ObjectMapper objectMapper; + DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; + DeduplicatorProperties props; + + + public ProcessedSpatDeduplicatorTopology(DeduplicatorProperties props, Properties streamsProperties){ + this.props = props; + this.streamsProperties = streamsProperties; + this.objectMapper = DateJsonMapper.getInstance(); + } + + + + public void start() { + if (streams != null && streams.state().isRunningOrRebalancing()) { + throw new IllegalStateException("Start called while streams is already running."); + } + Topology topology = buildTopology(); + streams = new KafkaStreams(topology, streamsProperties); + if (exceptionHandler != null) streams.setUncaughtExceptionHandler(exceptionHandler); + if (stateListener != null) streams.setStateListener(stateListener); + logger.info("Starting Map Deduplicator Topology"); + streams.start(); + } + + public Topology buildTopology() { + StreamsBuilder builder = new StreamsBuilder(); + + KStream inputStream = builder.stream(props.getKafkaTopicProcessedSpat(), Consumed.with(Serdes.String(), JsonSerdes.ProcessedSpat())); + + builder.addStateStore(Stores.keyValueStoreBuilder(Stores.persistentKeyValueStore(props.getKafkaStateStoreProcessedSpatName()), + Serdes.String(), JsonSerdes.ProcessedSpat())); + + KStream deduplicatedStream = inputStream.process(new ProcessedSpatProcessorSupplier(props), props.getKafkaStateStoreProcessedSpatName()); + + deduplicatedStream.print(Printed.toSysOut()); + + deduplicatedStream.to(props.getKafkaTopicDeduplicatedProcessedSpat(), Produced.with(Serdes.String(), JsonSerdes.ProcessedSpat())); + + return builder.build(); + + } + + public void stop() { + logger.info("Stopping Processed SPaT deduplicator Socket Broadcast Topology."); + if (streams != null) { + streams.close(); + streams.cleanUp(); + streams = null; + } + logger.info("Stopped Processed SPaT deduplicator Socket Broadcast Topology."); + } + + StateListener stateListener; + public void registerStateListener(StateListener stateListener) { + this.stateListener = stateListener; + } + + StreamsUncaughtExceptionHandler exceptionHandler; + public void registerUncaughtExceptionHandler(StreamsUncaughtExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/TimDeduplicatorTopology.java b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/TimDeduplicatorTopology.java new file mode 100644 index 0000000..d8cf570 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/java/us/dot/its/jpo/deduplicator/deduplicator/topologies/TimDeduplicatorTopology.java @@ -0,0 +1,126 @@ +package us.dot.its.jpo.deduplicator.deduplicator.topologies; + +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.KafkaStreams; +import org.apache.kafka.streams.StreamsBuilder; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.KafkaStreams.StateListener; +import org.apache.kafka.streams.errors.StreamsUncaughtExceptionHandler; + +import us.dot.its.jpo.deduplicator.deduplicator.serialization.JsonSerdes; +import us.dot.its.jpo.geojsonconverter.DateJsonMapper; + +import org.apache.kafka.streams.kstream.*; +import org.apache.kafka.streams.state.Stores; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.time.format.DateTimeFormatter; +import java.util.Properties; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.processors.suppliers.OdeTimJsonProcessorSupplier; + + +public class TimDeduplicatorTopology { + + private static final Logger logger = LoggerFactory.getLogger(TimDeduplicatorTopology.class); + + Topology topology; + KafkaStreams streams; + Properties streamsProperties; + ObjectMapper objectMapper; + DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; + DeduplicatorProperties props; + + public TimDeduplicatorTopology(DeduplicatorProperties props, Properties streamsProperties) { + this.props = props; + this.streamsProperties = streamsProperties; + this.objectMapper = DateJsonMapper.getInstance(); + } + + public void start() { + if (streams != null && streams.state().isRunningOrRebalancing()) { + throw new IllegalStateException("Start called while streams is already running."); + } + Topology topology = buildTopology(); + streams = new KafkaStreams(topology, streamsProperties); + if (exceptionHandler != null) + streams.setUncaughtExceptionHandler(exceptionHandler); + if (stateListener != null) + streams.setStateListener(stateListener); + logger.info("Starting Tim Deduplicator Topology"); + streams.start(); + } + + public JsonNode genJsonNode() { + return objectMapper.createObjectNode(); + } + + + + public Topology buildTopology() { + StreamsBuilder builder = new StreamsBuilder(); + + KStream inputStream = builder.stream(props.getKafkaTopicOdeTimJson(), + Consumed.with(Serdes.Void(), JsonSerdes.JSON())); + + builder.addStateStore(Stores.keyValueStoreBuilder(Stores.persistentKeyValueStore(props.getKafkaStateStoreOdeTimJsonName()), + Serdes.String(), JsonSerdes.JSON())); + + + + KStream timRekeyedStream = inputStream.selectKey((key, value) -> { + try { + + JsonNode travellerInformation = value.get("payload") + .get("data") + .get("MessageFrame") + .get("value") + .get("TravelerInformation"); + + String rsuIP = value.get("metadata").get("originIp").asText(); + String packetId = travellerInformation.get("packetID").asText(); + String msgCnt = travellerInformation.get("msgCnt").asText(); + + String newKey = rsuIP + "_" + packetId + "_" + msgCnt; + return newKey; + } catch (Exception e) { + return ""; + } + }).repartition(Repartitioned.with(Serdes.String(), JsonSerdes.JSON())); + + KStream deduplicatedStream = timRekeyedStream.process(new OdeTimJsonProcessorSupplier(props), props.getKafkaStateStoreOdeTimJsonName()); + + deduplicatedStream.to(props.getKafkaTopicDeduplicatedOdeTimJson(), Produced.with(Serdes.String(), JsonSerdes.JSON())); + + return builder.build(); + + } + + public void stop() { + logger.info("Stopping Tim deduplicator Socket Broadcast Topology."); + if (streams != null) { + streams.close(); + streams.cleanUp(); + streams = null; + } + logger.info("Stopped Tim deduplicator Socket Broadcast Topology."); + } + + StateListener stateListener; + + public void registerStateListener(StateListener stateListener) { + this.stateListener = stateListener; + } + + StreamsUncaughtExceptionHandler exceptionHandler; + + public void registerUncaughtExceptionHandler(StreamsUncaughtExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } + +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/resources/application.yaml b/jpo-deduplicator/jpo-deduplicator/src/main/resources/application.yaml new file mode 100644 index 0000000..3102c6b --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/resources/application.yaml @@ -0,0 +1,96 @@ +#General Properties +#================== +groupId: ^project.groupId^ +artifactId: ^project.artifactId^ +version: ^project.version^ +server.port: 8085 + +# Kafka properties +spring.kafka.bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:localhost:9092} +logging.level.org.apache.kafka: INFO +logging.level: INFO +log4j.logger.kafka: OFF +log4j.logger.org.apache.kafka: OFF + + + +# Processed Map Configuration +kafkaTopicProcessedMap: topic.ProcessedMap +kafkaTopicDeduplicatedProcessedMap: topic.DeduplicatedProcessedMap +enableProcessedMapDeduplication: true + +# Processed Map WKT Configuration +kafkaTopicProcessedMapWKT: topic.ProcessedMapWKT +kafkaTopicDeduplicatedProcessedMapWKT: topic.DeduplicatedProcessedMapWKT +enableProcessedMapWktDeduplication: true + +# Ode Map Json Configuration +kafkaTopicOdeMapJson: topic.OdeMapJson +kafkaTopicDeduplicatedOdeMapJson: topic.DeduplicatedOdeMapJson +enableOdeMapDeduplication: true + +# Ode Tim Json Configuration +kafkaTopicOdeTimJson: topic.OdeTimJson +kafkaTopicDeduplicatedOdeTimJson: topic.DeduplicatedOdeTimJson +enableOdeTimDeduplication: true + +# Ode Raw Encoded Tim Json Configuration +kafkaTopicOdeRawEncodedTimJson: topic.OdeRawEncodedTIMJson +kafkaTopicDeduplicatedOdeRawEncodedTimJson: topic.DeduplicatedOdeRawEncodedTIMJson +enableOdeRawEncodedTimDeduplication: true + +# Ode Bsm Json Configuration +kafkaTopicOdeBsmJson: topic.OdeBsmJson +kafkaTopicDeduplicatedOdeBsmJson: topic.DeduplicatedOdeBsmJson +enableOdeBsmDeduplication: true +odeBsmMaximumTimeDelta: 10000 # Milliseconds +odeBsmMaximumPositionDelta: 1 # Meter +odeBsmAlwaysIncludeAtSpeed: 1 # Meter / Second + +# Processed Map Configuration +kafkaTopicProcessedSpat: topic.ProcessedSpat +kafkaTopicDeduplicatedProcessedSpat: topic.DeduplicatedProcessedSpat +enableProcessedSpatDeduplication: true + + +# Amount of time to wait to try and increase batching +kafka.linger_ms: 50 + +# Custom kafka properties +kafka.topics: + autoCreateTopics: true # Override auto-create in test properties + numPartitions: 1 + numReplicas: 1 + createTopics: + - name: ${kafkaTopicProcessedMap} + cleanupPolicy: delete + retentionMs: 300000 + - name: ${kafkaTopicDeduplicatedProcessedMap} + cleanupPolicy: delete + retentionMs: 300000 + - name: ${kafkaTopicProcessedMapWKT} + cleanupPolicy: delete + retentionMs: 300000 + - name: ${kafkaTopicDeduplicatedProcessedMapWKT} + cleanupPolicy: delete + retentionMs: 300000 + - name: ${kafkaTopicOdeMapJson} + cleanupPolicy: delete + retentionMs: 300000 + - name: ${kafkaTopicDeduplicatedOdeMapJson} + cleanupPolicy: delete + retentionMs: 300000 + - name: ${kafkaTopicOdeTimJson} + cleanupPolicy: delete + retentionMs: 300000 + - name: ${kafkaTopicDeduplicatedOdeTimJson} + cleanupPolicy: delete + retentionMs: 300000 + - name: ${kafkaTopicProcessedSpat} + cleanupPolicy: delete + retentionMs: 300000 + - name: ${kafkaTopicDeduplicatedProcessedSpat} + cleanupPolicy: delete + retentionMs: 300000 + + \ No newline at end of file diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/resources/logback.xml b/jpo-deduplicator/jpo-deduplicator/src/main/resources/logback.xml new file mode 100644 index 0000000..860df15 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/main/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + + %date{"yyyy-MM-dd HH:mm:ss", UTC} [%thread] %-5level %logger{0} - %msg %n + + + + + + + + + + + + + diff --git a/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/BsmDeduplicatorTopologyTest.java b/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/BsmDeduplicatorTopologyTest.java new file mode 100644 index 0000000..98d6204 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/BsmDeduplicatorTopologyTest.java @@ -0,0 +1,116 @@ +package deduplicator; + + + +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.TestInputTopic; +import org.apache.kafka.streams.TestOutputTopic; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.TopologyTestDriver; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import us.dot.its.jpo.conflictmonitor.monitor.serialization.JsonSerdes; +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.topologies.BsmDeduplicatorTopology; +import us.dot.its.jpo.ode.model.OdeBsmData; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + + +public class BsmDeduplicatorTopologyTest { + + String inputTopic = "topic.OdeBsmJson"; + String outputTopic = "topic.DeduplicatedOdeBsmJson"; + ObjectMapper objectMapper; + + String inputBsm1 = "{\"metadata\":{\"bsmSource\":\"RV\",\"logFileName\":\"\",\"recordType\":\"bsmTx\",\"securityResultCode\":\"success\",\"receivedMessageDetails\":{\"locationData\":{\"latitude\":\"\",\"longitude\":\"\",\"elevation\":\"\",\"speed\":\"\",\"heading\":\"\"},\"rxSource\":\"RV\"},\"encodings\":null,\"payloadType\":\"us.dot.its.jpo.ode.model.OdeBsmPayload\",\"serialId\":{\"streamId\":\"f1bfed26-d986-4a0c-b8a4-68b1e5ac1348\",\"bundleSize\":1,\"bundleId\":0,\"recordId\":0,\"serialNumber\":0},\"odeReceivedAt\":\"2024-07-01T15:00:52.597Z\",\"schemaVersion\":6,\"maxDurationTime\":0,\"recordGeneratedAt\":\"\",\"recordGeneratedBy\":null,\"sanitized\":false,\"odePacketID\":\"\",\"odeTimStartDateTime\":\"\",\"originIp\":\"10.164.6.18\"},\"payload\":{\"data\":{\"coreData\":{\"msgCnt\":7,\"id\":\"E79423A3\",\"secMark\":52597,\"position\":{\"latitude\":40.2970849,\"longitude\":-111.6956069,\"elevation\":1439},\"accelSet\":{\"accelLat\":2001,\"accelLong\":0,\"accelVert\":-127,\"accelYaw\":0},\"accuracy\":{\"semiMajor\":2,\"semiMinor\":2,\"orientation\":44.49530799},\"transmission\":\"FORWARDGEARS\",\"speed\":0,\"heading\":24.2,\"angle\":0,\"brakes\":{\"wheelBrakes\":{\"leftFront\":true,\"rightFront\":true,\"unavailable\":false,\"leftRear\":true,\"rightRear\":true},\"traction\":\"on\",\"abs\":\"on\",\"scs\":\"on\",\"brakeBoost\":\"off\",\"auxBrakes\":\"unavailable\"},\"size\":{\"width\":230,\"length\":500}},\"partII\":[{\"id\":\"VehicleSafetyExtensions\",\"value\":{\"events\":null,\"pathHistory\":{\"initialPosition\":null,\"currGNSSstatus\":null,\"crumbData\":[{\"elevationOffset\":-0.5,\"heading\":null,\"latOffset\":0.0000038,\"lonOffset\":0.0001137,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":31.41},{\"elevationOffset\":-0.4,\"heading\":null,\"latOffset\":0.0000339,\"lonOffset\":0.0001695,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":32.05},{\"elevationOffset\":0,\"heading\":null,\"latOffset\":0.000184,\"lonOffset\":0.0002106,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":36.29},{\"elevationOffset\":0,\"heading\":null,\"latOffset\":0.0003092,\"lonOffset\":0.0003081,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":37.22},{\"elevationOffset\":0.4,\"heading\":null,\"latOffset\":0.0004354,\"lonOffset\":0.0003906,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":38.15},{\"elevationOffset\":1.4,\"heading\":null,\"latOffset\":0.0007727,\"lonOffset\":0.0004391,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":40.39},{\"elevationOffset\":1.4,\"heading\":null,\"latOffset\":0.00084,\"lonOffset\":0.0004778,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":41.61},{\"elevationOffset\":1.4,\"heading\":null,\"latOffset\":0.0008649,\"lonOffset\":0.0005765,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":43.48},{\"elevationOffset\":1.7,\"heading\":null,\"latOffset\":0.0008086,\"lonOffset\":0.0015482,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":68.53},{\"elevationOffset\":1.7,\"heading\":null,\"latOffset\":0.0007738,\"lonOffset\":0.0015944,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":72.09},{\"elevationOffset\":2.1,\"heading\":null,\"latOffset\":0.0007349,\"lonOffset\":0.0015747,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":76.97}]},\"pathPrediction\":{\"confidence\":100,\"radiusOfCurve\":0},\"lights\":null}},{\"id\":\"SupplementalVehicleExtensions\",\"value\":{\"classification\":66,\"classDetails\":{\"fuelType\":null,\"hpmsType\":\"none\",\"iso3883\":null,\"keyType\":66,\"responderType\":null,\"responseEquip\":null,\"role\":null,\"vehicleType\":null},\"vehicleData\":{\"bumpers\":null,\"height\":1.8,\"mass\":2800,\"trailerWeight\":null},\"weatherReport\":{\"friction\":null,\"isRaining\":\"ERROR\",\"precipSituation\":\"UNKNOWN\",\"rainRate\":null,\"roadFriction\":0,\"solarRadiation\":null},\"weatherProbe\":{\"airPressure\":860,\"airTemp\":71,\"rainRates\":null},\"obstacle\":null,\"status\":null,\"speedProfile\":null,\"theRTCM\":null}}]},\"dataType\":\"us.dot.its.jpo.ode.plugin.j2735.J2735Bsm\"}}"; + + // Same as BSM 1 - No Message should be generated + String inputBsm2 = "{\"metadata\":{\"bsmSource\":\"RV\",\"logFileName\":\"\",\"recordType\":\"bsmTx\",\"securityResultCode\":\"success\",\"receivedMessageDetails\":{\"locationData\":{\"latitude\":\"\",\"longitude\":\"\",\"elevation\":\"\",\"speed\":\"\",\"heading\":\"\"},\"rxSource\":\"RV\"},\"encodings\":null,\"payloadType\":\"us.dot.its.jpo.ode.model.OdeBsmPayload\",\"serialId\":{\"streamId\":\"f1bfed26-d986-4a0c-b8a4-68b1e5ac1348\",\"bundleSize\":1,\"bundleId\":0,\"recordId\":0,\"serialNumber\":0},\"odeReceivedAt\":\"2024-07-01T15:00:52.697Z\",\"schemaVersion\":6,\"maxDurationTime\":0,\"recordGeneratedAt\":\"\",\"recordGeneratedBy\":null,\"sanitized\":false,\"odePacketID\":\"\",\"odeTimStartDateTime\":\"\",\"originIp\":\"10.164.6.18\"},\"payload\":{\"data\":{\"coreData\":{\"msgCnt\":7,\"id\":\"E79423A3\",\"secMark\":52597,\"position\":{\"latitude\":40.2970849,\"longitude\":-111.6956069,\"elevation\":1439},\"accelSet\":{\"accelLat\":2001,\"accelLong\":0,\"accelVert\":-127,\"accelYaw\":0},\"accuracy\":{\"semiMajor\":2,\"semiMinor\":2,\"orientation\":44.49530799},\"transmission\":\"FORWARDGEARS\",\"speed\":0,\"heading\":24.2,\"angle\":0,\"brakes\":{\"wheelBrakes\":{\"leftFront\":true,\"rightFront\":true,\"unavailable\":false,\"leftRear\":true,\"rightRear\":true},\"traction\":\"on\",\"abs\":\"on\",\"scs\":\"on\",\"brakeBoost\":\"off\",\"auxBrakes\":\"unavailable\"},\"size\":{\"width\":230,\"length\":500}},\"partII\":[{\"id\":\"VehicleSafetyExtensions\",\"value\":{\"events\":null,\"pathHistory\":{\"initialPosition\":null,\"currGNSSstatus\":null,\"crumbData\":[{\"elevationOffset\":-0.5,\"heading\":null,\"latOffset\":0.0000038,\"lonOffset\":0.0001137,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":31.41},{\"elevationOffset\":-0.4,\"heading\":null,\"latOffset\":0.0000339,\"lonOffset\":0.0001695,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":32.05},{\"elevationOffset\":0,\"heading\":null,\"latOffset\":0.000184,\"lonOffset\":0.0002106,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":36.29},{\"elevationOffset\":0,\"heading\":null,\"latOffset\":0.0003092,\"lonOffset\":0.0003081,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":37.22},{\"elevationOffset\":0.4,\"heading\":null,\"latOffset\":0.0004354,\"lonOffset\":0.0003906,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":38.15},{\"elevationOffset\":1.4,\"heading\":null,\"latOffset\":0.0007727,\"lonOffset\":0.0004391,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":40.39},{\"elevationOffset\":1.4,\"heading\":null,\"latOffset\":0.00084,\"lonOffset\":0.0004778,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":41.61},{\"elevationOffset\":1.4,\"heading\":null,\"latOffset\":0.0008649,\"lonOffset\":0.0005765,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":43.48},{\"elevationOffset\":1.7,\"heading\":null,\"latOffset\":0.0008086,\"lonOffset\":0.0015482,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":68.53},{\"elevationOffset\":1.7,\"heading\":null,\"latOffset\":0.0007738,\"lonOffset\":0.0015944,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":72.09},{\"elevationOffset\":2.1,\"heading\":null,\"latOffset\":0.0007349,\"lonOffset\":0.0015747,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":76.97}]},\"pathPrediction\":{\"confidence\":100,\"radiusOfCurve\":0},\"lights\":null}},{\"id\":\"SupplementalVehicleExtensions\",\"value\":{\"classification\":66,\"classDetails\":{\"fuelType\":null,\"hpmsType\":\"none\",\"iso3883\":null,\"keyType\":66,\"responderType\":null,\"responseEquip\":null,\"role\":null,\"vehicleType\":null},\"vehicleData\":{\"bumpers\":null,\"height\":1.8,\"mass\":2800,\"trailerWeight\":null},\"weatherReport\":{\"friction\":null,\"isRaining\":\"ERROR\",\"precipSituation\":\"UNKNOWN\",\"rainRate\":null,\"roadFriction\":0,\"solarRadiation\":null},\"weatherProbe\":{\"airPressure\":860,\"airTemp\":71,\"rainRates\":null},\"obstacle\":null,\"status\":null,\"speedProfile\":null,\"theRTCM\":null}}]},\"dataType\":\"us.dot.its.jpo.ode.plugin.j2735.J2735Bsm\"}}"; + + // Increase Time from Bsm 1 + String inputBsm3 = "{\"metadata\":{\"bsmSource\":\"RV\",\"logFileName\":\"\",\"recordType\":\"bsmTx\",\"securityResultCode\":\"success\",\"receivedMessageDetails\":{\"locationData\":{\"latitude\":\"\",\"longitude\":\"\",\"elevation\":\"\",\"speed\":\"\",\"heading\":\"\"},\"rxSource\":\"RV\"},\"encodings\":null,\"payloadType\":\"us.dot.its.jpo.ode.model.OdeBsmPayload\",\"serialId\":{\"streamId\":\"f1bfed26-d986-4a0c-b8a4-68b1e5ac1348\",\"bundleSize\":1,\"bundleId\":0,\"recordId\":0,\"serialNumber\":0},\"odeReceivedAt\":\"2024-07-01T15:01:02.797Z\",\"schemaVersion\":6,\"maxDurationTime\":0,\"recordGeneratedAt\":\"\",\"recordGeneratedBy\":null,\"sanitized\":false,\"odePacketID\":\"\",\"odeTimStartDateTime\":\"\",\"originIp\":\"10.164.6.18\"},\"payload\":{\"data\":{\"coreData\":{\"msgCnt\":7,\"id\":\"E79423A3\",\"secMark\":52597,\"position\":{\"latitude\":40.2970849,\"longitude\":-111.6956069,\"elevation\":1439},\"accelSet\":{\"accelLat\":2001,\"accelLong\":0,\"accelVert\":-127,\"accelYaw\":0},\"accuracy\":{\"semiMajor\":2,\"semiMinor\":2,\"orientation\":44.49530799},\"transmission\":\"FORWARDGEARS\",\"speed\":0,\"heading\":24.2,\"angle\":0,\"brakes\":{\"wheelBrakes\":{\"leftFront\":true,\"rightFront\":true,\"unavailable\":false,\"leftRear\":true,\"rightRear\":true},\"traction\":\"on\",\"abs\":\"on\",\"scs\":\"on\",\"brakeBoost\":\"off\",\"auxBrakes\":\"unavailable\"},\"size\":{\"width\":230,\"length\":500}},\"partII\":[{\"id\":\"VehicleSafetyExtensions\",\"value\":{\"events\":null,\"pathHistory\":{\"initialPosition\":null,\"currGNSSstatus\":null,\"crumbData\":[{\"elevationOffset\":-0.5,\"heading\":null,\"latOffset\":0.0000038,\"lonOffset\":0.0001137,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":31.41},{\"elevationOffset\":-0.4,\"heading\":null,\"latOffset\":0.0000339,\"lonOffset\":0.0001695,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":32.05},{\"elevationOffset\":0,\"heading\":null,\"latOffset\":0.000184,\"lonOffset\":0.0002106,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":36.29},{\"elevationOffset\":0,\"heading\":null,\"latOffset\":0.0003092,\"lonOffset\":0.0003081,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":37.22},{\"elevationOffset\":0.4,\"heading\":null,\"latOffset\":0.0004354,\"lonOffset\":0.0003906,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":38.15},{\"elevationOffset\":1.4,\"heading\":null,\"latOffset\":0.0007727,\"lonOffset\":0.0004391,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":40.39},{\"elevationOffset\":1.4,\"heading\":null,\"latOffset\":0.00084,\"lonOffset\":0.0004778,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":41.61},{\"elevationOffset\":1.4,\"heading\":null,\"latOffset\":0.0008649,\"lonOffset\":0.0005765,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":43.48},{\"elevationOffset\":1.7,\"heading\":null,\"latOffset\":0.0008086,\"lonOffset\":0.0015482,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":68.53},{\"elevationOffset\":1.7,\"heading\":null,\"latOffset\":0.0007738,\"lonOffset\":0.0015944,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":72.09},{\"elevationOffset\":2.1,\"heading\":null,\"latOffset\":0.0007349,\"lonOffset\":0.0015747,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":76.97}]},\"pathPrediction\":{\"confidence\":100,\"radiusOfCurve\":0},\"lights\":null}},{\"id\":\"SupplementalVehicleExtensions\",\"value\":{\"classification\":66,\"classDetails\":{\"fuelType\":null,\"hpmsType\":\"none\",\"iso3883\":null,\"keyType\":66,\"responderType\":null,\"responseEquip\":null,\"role\":null,\"vehicleType\":null},\"vehicleData\":{\"bumpers\":null,\"height\":1.8,\"mass\":2800,\"trailerWeight\":null},\"weatherReport\":{\"friction\":null,\"isRaining\":\"ERROR\",\"precipSituation\":\"UNKNOWN\",\"rainRate\":null,\"roadFriction\":0,\"solarRadiation\":null},\"weatherProbe\":{\"airPressure\":860,\"airTemp\":71,\"rainRates\":null},\"obstacle\":null,\"status\":null,\"speedProfile\":null,\"theRTCM\":null}}]},\"dataType\":\"us.dot.its.jpo.ode.plugin.j2735.J2735Bsm\"}}"; + + // Vehicle Speed not 0 + String inputBsm4 = "{\"metadata\":{\"bsmSource\":\"RV\",\"logFileName\":\"\",\"recordType\":\"bsmTx\",\"securityResultCode\":\"success\",\"receivedMessageDetails\":{\"locationData\":{\"latitude\":\"\",\"longitude\":\"\",\"elevation\":\"\",\"speed\":\"\",\"heading\":\"\"},\"rxSource\":\"RV\"},\"encodings\":null,\"payloadType\":\"us.dot.its.jpo.ode.model.OdeBsmPayload\",\"serialId\":{\"streamId\":\"f1bfed26-d986-4a0c-b8a4-68b1e5ac1348\",\"bundleSize\":1,\"bundleId\":0,\"recordId\":0,\"serialNumber\":0},\"odeReceivedAt\":\"2024-07-01T15:01:02.897Z\",\"schemaVersion\":6,\"maxDurationTime\":0,\"recordGeneratedAt\":\"\",\"recordGeneratedBy\":null,\"sanitized\":false,\"odePacketID\":\"\",\"odeTimStartDateTime\":\"\",\"originIp\":\"10.164.6.18\"},\"payload\":{\"data\":{\"coreData\":{\"msgCnt\":7,\"id\":\"E79423A3\",\"secMark\":52597,\"position\":{\"latitude\":40.2970849,\"longitude\":-111.6956069,\"elevation\":1439},\"accelSet\":{\"accelLat\":2001,\"accelLong\":0,\"accelVert\":-127,\"accelYaw\":0},\"accuracy\":{\"semiMajor\":2,\"semiMinor\":2,\"orientation\":44.49530799},\"transmission\":\"FORWARDGEARS\",\"speed\":10,\"heading\":24.2,\"angle\":0,\"brakes\":{\"wheelBrakes\":{\"leftFront\":true,\"rightFront\":true,\"unavailable\":false,\"leftRear\":true,\"rightRear\":true},\"traction\":\"on\",\"abs\":\"on\",\"scs\":\"on\",\"brakeBoost\":\"off\",\"auxBrakes\":\"unavailable\"},\"size\":{\"width\":230,\"length\":500}},\"partII\":[{\"id\":\"VehicleSafetyExtensions\",\"value\":{\"events\":null,\"pathHistory\":{\"initialPosition\":null,\"currGNSSstatus\":null,\"crumbData\":[{\"elevationOffset\":-0.5,\"heading\":null,\"latOffset\":0.0000038,\"lonOffset\":0.0001137,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":31.41},{\"elevationOffset\":-0.4,\"heading\":null,\"latOffset\":0.0000339,\"lonOffset\":0.0001695,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":32.05},{\"elevationOffset\":0,\"heading\":null,\"latOffset\":0.000184,\"lonOffset\":0.0002106,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":36.29},{\"elevationOffset\":0,\"heading\":null,\"latOffset\":0.0003092,\"lonOffset\":0.0003081,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":37.22},{\"elevationOffset\":0.4,\"heading\":null,\"latOffset\":0.0004354,\"lonOffset\":0.0003906,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":38.15},{\"elevationOffset\":1.4,\"heading\":null,\"latOffset\":0.0007727,\"lonOffset\":0.0004391,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":40.39},{\"elevationOffset\":1.4,\"heading\":null,\"latOffset\":0.00084,\"lonOffset\":0.0004778,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":41.61},{\"elevationOffset\":1.4,\"heading\":null,\"latOffset\":0.0008649,\"lonOffset\":0.0005765,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":43.48},{\"elevationOffset\":1.7,\"heading\":null,\"latOffset\":0.0008086,\"lonOffset\":0.0015482,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":68.53},{\"elevationOffset\":1.7,\"heading\":null,\"latOffset\":0.0007738,\"lonOffset\":0.0015944,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":72.09},{\"elevationOffset\":2.1,\"heading\":null,\"latOffset\":0.0007349,\"lonOffset\":0.0015747,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":76.97}]},\"pathPrediction\":{\"confidence\":100,\"radiusOfCurve\":0},\"lights\":null}},{\"id\":\"SupplementalVehicleExtensions\",\"value\":{\"classification\":66,\"classDetails\":{\"fuelType\":null,\"hpmsType\":\"none\",\"iso3883\":null,\"keyType\":66,\"responderType\":null,\"responseEquip\":null,\"role\":null,\"vehicleType\":null},\"vehicleData\":{\"bumpers\":null,\"height\":1.8,\"mass\":2800,\"trailerWeight\":null},\"weatherReport\":{\"friction\":null,\"isRaining\":\"ERROR\",\"precipSituation\":\"UNKNOWN\",\"rainRate\":null,\"roadFriction\":0,\"solarRadiation\":null},\"weatherProbe\":{\"airPressure\":860,\"airTemp\":71,\"rainRates\":null},\"obstacle\":null,\"status\":null,\"speedProfile\":null,\"theRTCM\":null}}]},\"dataType\":\"us.dot.its.jpo.ode.plugin.j2735.J2735Bsm\"}}"; + + // Vehicle Position has changed + String inputBsm5 = "{\"metadata\":{\"bsmSource\":\"RV\",\"logFileName\":\"\",\"recordType\":\"bsmTx\",\"securityResultCode\":\"success\",\"receivedMessageDetails\":{\"locationData\":{\"latitude\":\"\",\"longitude\":\"\",\"elevation\":\"\",\"speed\":\"\",\"heading\":\"\"},\"rxSource\":\"RV\"},\"encodings\":null,\"payloadType\":\"us.dot.its.jpo.ode.model.OdeBsmPayload\",\"serialId\":{\"streamId\":\"f1bfed26-d986-4a0c-b8a4-68b1e5ac1348\",\"bundleSize\":1,\"bundleId\":0,\"recordId\":0,\"serialNumber\":0},\"odeReceivedAt\":\"2024-07-01T15:01:02.997Z\",\"schemaVersion\":6,\"maxDurationTime\":0,\"recordGeneratedAt\":\"\",\"recordGeneratedBy\":null,\"sanitized\":false,\"odePacketID\":\"\",\"odeTimStartDateTime\":\"\",\"originIp\":\"10.164.6.18\"},\"payload\":{\"data\":{\"coreData\":{\"msgCnt\":7,\"id\":\"E79423A3\",\"secMark\":52597,\"position\":{\"latitude\":40.2970949,\"longitude\":-111.6956169,\"elevation\":1439},\"accelSet\":{\"accelLat\":2001,\"accelLong\":0,\"accelVert\":-127,\"accelYaw\":0},\"accuracy\":{\"semiMajor\":2,\"semiMinor\":2,\"orientation\":44.49530799},\"transmission\":\"FORWARDGEARS\",\"speed\":0,\"heading\":24.2,\"angle\":0,\"brakes\":{\"wheelBrakes\":{\"leftFront\":true,\"rightFront\":true,\"unavailable\":false,\"leftRear\":true,\"rightRear\":true},\"traction\":\"on\",\"abs\":\"on\",\"scs\":\"on\",\"brakeBoost\":\"off\",\"auxBrakes\":\"unavailable\"},\"size\":{\"width\":230,\"length\":500}},\"partII\":[{\"id\":\"VehicleSafetyExtensions\",\"value\":{\"events\":null,\"pathHistory\":{\"initialPosition\":null,\"currGNSSstatus\":null,\"crumbData\":[{\"elevationOffset\":-0.5,\"heading\":null,\"latOffset\":0.0000038,\"lonOffset\":0.0001137,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":31.41},{\"elevationOffset\":-0.4,\"heading\":null,\"latOffset\":0.0000339,\"lonOffset\":0.0001695,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":32.05},{\"elevationOffset\":0,\"heading\":null,\"latOffset\":0.000184,\"lonOffset\":0.0002106,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":36.29},{\"elevationOffset\":0,\"heading\":null,\"latOffset\":0.0003092,\"lonOffset\":0.0003081,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":37.22},{\"elevationOffset\":0.4,\"heading\":null,\"latOffset\":0.0004354,\"lonOffset\":0.0003906,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":38.15},{\"elevationOffset\":1.4,\"heading\":null,\"latOffset\":0.0007727,\"lonOffset\":0.0004391,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":40.39},{\"elevationOffset\":1.4,\"heading\":null,\"latOffset\":0.00084,\"lonOffset\":0.0004778,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":41.61},{\"elevationOffset\":1.4,\"heading\":null,\"latOffset\":0.0008649,\"lonOffset\":0.0005765,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":43.48},{\"elevationOffset\":1.7,\"heading\":null,\"latOffset\":0.0008086,\"lonOffset\":0.0015482,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":68.53},{\"elevationOffset\":1.7,\"heading\":null,\"latOffset\":0.0007738,\"lonOffset\":0.0015944,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":72.09},{\"elevationOffset\":2.1,\"heading\":null,\"latOffset\":0.0007349,\"lonOffset\":0.0015747,\"posAccuracy\":null,\"speed\":null,\"timeOffset\":76.97}]},\"pathPrediction\":{\"confidence\":100,\"radiusOfCurve\":0},\"lights\":null}},{\"id\":\"SupplementalVehicleExtensions\",\"value\":{\"classification\":66,\"classDetails\":{\"fuelType\":null,\"hpmsType\":\"none\",\"iso3883\":null,\"keyType\":66,\"responderType\":null,\"responseEquip\":null,\"role\":null,\"vehicleType\":null},\"vehicleData\":{\"bumpers\":null,\"height\":1.8,\"mass\":2800,\"trailerWeight\":null},\"weatherReport\":{\"friction\":null,\"isRaining\":\"ERROR\",\"precipSituation\":\"UNKNOWN\",\"rainRate\":null,\"roadFriction\":0,\"solarRadiation\":null},\"weatherProbe\":{\"airPressure\":860,\"airTemp\":71,\"rainRates\":null},\"obstacle\":null,\"status\":null,\"speedProfile\":null,\"theRTCM\":null}}]},\"dataType\":\"us.dot.its.jpo.ode.plugin.j2735.J2735Bsm\"}}"; + + + @Autowired + DeduplicatorProperties props; + + + + @Test + public void testTopology() { + + + + + + props = new DeduplicatorProperties(); + props.setAlwaysIncludeAtSpeed(1); + props.setenableOdeBsmDeduplication(true); + props.setMaximumPositionDelta(1); + props.setMaximumTimeDelta(10000); + props.setkafkaTopicOdeBsmJson(inputTopic); + props.setkafkaTopicDeduplicatedOdeBsmJson(outputTopic); + + BsmDeduplicatorTopology bsmDeduplicatorTopology = new BsmDeduplicatorTopology(props); + Topology topology = bsmDeduplicatorTopology.buildTopology(); + + try (TopologyTestDriver driver = new TopologyTestDriver(topology)) { + + + TestInputTopic inputOdeBsmData = driver.createInputTopic( + inputTopic, + Serdes.Void().serializer(), + Serdes.String().serializer()); + + + TestOutputTopic outputOdeBsmData = driver.createOutputTopic( + outputTopic, + Serdes.String().deserializer(), + JsonSerdes.OdeBsm().deserializer()); + + inputOdeBsmData.pipeInput(null, inputBsm1); + inputOdeBsmData.pipeInput(null, inputBsm2); + inputOdeBsmData.pipeInput(null, inputBsm3); + inputOdeBsmData.pipeInput(null, inputBsm4); + inputOdeBsmData.pipeInput(null, inputBsm5); + + List> bsmDeduplicationResults = outputOdeBsmData.readKeyValuesToList(); + + // validate that only 3 messages make it through + assertEquals(4, bsmDeduplicationResults.size()); + + objectMapper = new ObjectMapper(); + OdeBsmData bsm1 = objectMapper.readValue(inputBsm1, OdeBsmData.class); + OdeBsmData bsm3 = objectMapper.readValue(inputBsm3, OdeBsmData.class); + OdeBsmData bsm4 = objectMapper.readValue(inputBsm4, OdeBsmData.class); + OdeBsmData bsm5 = objectMapper.readValue(inputBsm5, OdeBsmData.class); + + + assertEquals(bsm1.getMetadata().getOdeReceivedAt(), bsmDeduplicationResults.get(0).value.getMetadata().getOdeReceivedAt()); + assertEquals(bsm3.getMetadata().getOdeReceivedAt(), bsmDeduplicationResults.get(1).value.getMetadata().getOdeReceivedAt()); + assertEquals(bsm4.getMetadata().getOdeReceivedAt(), bsmDeduplicationResults.get(2).value.getMetadata().getOdeReceivedAt()); + assertEquals(bsm5.getMetadata().getOdeReceivedAt(), bsmDeduplicationResults.get(3).value.getMetadata().getOdeReceivedAt()); + + } catch (JsonMappingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (JsonProcessingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/MapDeduplicatorTopologyTest.java b/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/MapDeduplicatorTopologyTest.java new file mode 100644 index 0000000..a339677 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/MapDeduplicatorTopologyTest.java @@ -0,0 +1,96 @@ +package deduplicator; + + + +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.TestInputTopic; +import org.apache.kafka.streams.TestOutputTopic; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.TopologyTestDriver; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.topologies.MapDeduplicatorTopology; +import us.dot.its.jpo.geojsonconverter.serialization.JsonSerdes; +import us.dot.its.jpo.ode.model.OdeMapData; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + + +public class MapDeduplicatorTopologyTest { + + String inputTopic = "topic.OdeMapJson"; + String outputTopic = "topic.DeduplicatedOdeMapJson"; + ObjectMapper objectMapper; + + String inputMap1 = "{\"metadata\":{\"logFileName\":\"\",\"recordType\":\"mapTx\",\"securityResultCode\":\"success\",\"receivedMessageDetails\":{\"locationData\":null,\"rxSource\":\"NA\"},\"encodings\":null,\"payloadType\":\"us.dot.its.jpo.ode.model.OdeMapPayload\",\"serialId\":{\"streamId\":\"139ba3af-e501-4854-9327-b9e1a2ed8dfb\",\"bundleSize\":1,\"bundleId\":0,\"recordId\":0,\"serialNumber\":0},\"odeReceivedAt\":\"2024-02-22T21:12:44.308Z\",\"schemaVersion\":6,\"maxDurationTime\":0,\"recordGeneratedAt\":\"\",\"recordGeneratedBy\":null,\"sanitized\":false,\"odePacketID\":\"\",\"odeTimStartDateTime\":\"\",\"mapSource\":\"RSU\",\"originIp\":\"10.11.81.12\"},\"payload\":{\"data\":{\"timeStamp\":null,\"msgIssueRevision\":2,\"layerType\":\"intersectionData\",\"layerID\":0,\"intersections\":{\"intersectionGeometry\":[{\"name\":null,\"id\":{\"region\":null,\"id\":12109},\"revision\":2,\"refPoint\":{\"latitude\":39.5880413,\"longitude\":-105.0908854,\"elevation\":1691},\"laneWidth\":366,\"speedLimits\":null,\"laneSet\":{\"GenericLane\":[{\"laneID\":1,\"name\":null,\"ingressApproach\":1,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":1511,\"y\":-1514},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":723,\"y\":-3116},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":892,\"y\":-3818},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":702,\"y\":-2507},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":681,\"y\":-2412},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":827,\"y\":-2798},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1176,\"y\":-3614},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":683,\"y\":-1992},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1000,\"y\":-2818},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":466,\"y\":-1295},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":2505,\"y\":-6164},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":6037,\"y\":-13380},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":70}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":2,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":2,\"name\":null,\"ingressApproach\":1,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":1192,\"y\":-1619},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":807,\"y\":-3733},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":1010,\"y\":-4226},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":1612,\"y\":-5477},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1142,\"y\":-3648},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1131,\"y\":-3343},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":2259,\"y\":-6170},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":2359,\"y\":-5737},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":5300,\"y\":-11774},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":50}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":2,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":3,\"name\":null,\"ingressApproach\":1,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":805,\"y\":-1704},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":380,\"y\":-1813},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":832,\"y\":-3451},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":{\"x\":202,\"y\":-872},\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":{\"x\":340,\"y\":-482},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":2,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":6,\"name\":null,\"ingressApproach\":null,\"egressApproach\":2,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-988,\"y\":-2151},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":{\"x\":69,\"y\":-329},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":5,\"name\":null,\"ingressApproach\":null,\"egressApproach\":2,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-630,\"y\":-2062},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":{\"x\":76,\"y\":-377},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":4,\"name\":null,\"ingressApproach\":null,\"egressApproach\":2,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-245,\"y\":-2001},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":{\"x\":76,\"y\":-349},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":10,\"name\":null,\"ingressApproach\":null,\"egressApproach\":4,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2374,\"y\":232},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":{\"x\":-357,\"y\":-96},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":8,\"name\":null,\"ingressApproach\":3,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2246,\"y\":-514},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2644,\"y\":-581},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2887,\"y\":-442},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3583,\"y\":-339},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3757,\"y\":27},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3249,\"y\":361},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2050,\"y\":478},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3893,\"y\":1184},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2625,\"y\":766},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2524,\"y\":607},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2280,\"y\":325},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":4,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":7,\"name\":null,\"ingressApproach\":3,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2216,\"y\":-915},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2322,\"y\":-471},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1877,\"y\":-349},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1787,\"y\":-235},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2666,\"y\":-102},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":6,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":null,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":9,\"name\":null,\"ingressApproach\":3,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2295,\"y\":-169},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2420,\"y\":-499},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1968,\"y\":-339},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1843,\"y\":-256},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2121,\"y\":-339},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":4,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":12,\"name\":null,\"ingressApproach\":5,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1364,\"y\":1705},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":-885,\"y\":4854},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-410,\"y\":2559},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-333,\"y\":1847},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-319,\"y\":3244},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":-500,\"y\":4510},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-333,\"y\":3403},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-268,\"y\":2982},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-758,\"y\":11212},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-323,\"y\":11176},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-70}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":5,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":6,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":13,\"name\":null,\"ingressApproach\":5,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-992,\"y\":1735},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":-896,\"y\":4816},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-326,\"y\":2227},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-333,\"y\":2366},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-410,\"y\":3030},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-326,\"y\":3009},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-271,\"y\":2518},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-264,\"y\":2857},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-243,\"y\":2504},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-740,\"y\":11165},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-253,\"y\":11359},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-70}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":4,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":6,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":11,\"name\":null,\"ingressApproach\":5,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1744,\"y\":1607},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-563,\"y\":3136},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-352,\"y\":2336},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-223,\"y\":1407},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-155,\"y\":1778},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":null,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":14,\"name\":null,\"ingressApproach\":null,\"egressApproach\":6,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":398,\"y\":1931},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":{\"x\":-76,\"y\":356},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":15,\"name\":null,\"ingressApproach\":null,\"egressApproach\":6,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":838,\"y\":1985},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":{\"x\":-89,\"y\":349},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null}]}}]},\"roadSegments\":null,\"dataParameters\":null,\"restrictionList\":null},\"dataType\":\"us.dot.its.jpo.ode.plugin.j2735.J2735MAP\"}}"; + String inputMap2 = "{\"metadata\":{\"logFileName\":\"\",\"recordType\":\"mapTx\",\"securityResultCode\":\"success\",\"receivedMessageDetails\":{\"locationData\":null,\"rxSource\":\"NA\"},\"encodings\":null,\"payloadType\":\"us.dot.its.jpo.ode.model.OdeMapPayload\",\"serialId\":{\"streamId\":\"139ba3af-e501-4854-9327-b9e1a2ed8dfb\",\"bundleSize\":1,\"bundleId\":0,\"recordId\":0,\"serialNumber\":0},\"odeReceivedAt\":\"2024-02-22T21:12:45.322Z\",\"schemaVersion\":6,\"maxDurationTime\":0,\"recordGeneratedAt\":\"\",\"recordGeneratedBy\":null,\"sanitized\":false,\"odePacketID\":\"\",\"odeTimStartDateTime\":\"\",\"mapSource\":\"RSU\",\"originIp\":\"10.11.81.12\"},\"payload\":{\"data\":{\"timeStamp\":null,\"msgIssueRevision\":2,\"layerType\":\"intersectionData\",\"layerID\":0,\"intersections\":{\"intersectionGeometry\":[{\"name\":null,\"id\":{\"region\":null,\"id\":12109},\"revision\":2,\"refPoint\":{\"latitude\":39.5880413,\"longitude\":-105.0908854,\"elevation\":1691},\"laneWidth\":366,\"speedLimits\":null,\"laneSet\":{\"GenericLane\":[{\"laneID\":1,\"name\":null,\"ingressApproach\":1,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":1511,\"y\":-1514},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":723,\"y\":-3116},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":892,\"y\":-3818},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":702,\"y\":-2507},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":681,\"y\":-2412},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":827,\"y\":-2798},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1176,\"y\":-3614},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":683,\"y\":-1992},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1000,\"y\":-2818},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":466,\"y\":-1295},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":2505,\"y\":-6164},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":6037,\"y\":-13380},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":70}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":2,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":2,\"name\":null,\"ingressApproach\":1,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":1192,\"y\":-1619},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":807,\"y\":-3733},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":1010,\"y\":-4226},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":1612,\"y\":-5477},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1142,\"y\":-3648},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1131,\"y\":-3343},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":2259,\"y\":-6170},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":2359,\"y\":-5737},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":5300,\"y\":-11774},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":50}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":2,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":3,\"name\":null,\"ingressApproach\":1,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":805,\"y\":-1704},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":380,\"y\":-1813},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":832,\"y\":-3451},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":{\"x\":202,\"y\":-872},\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":{\"x\":340,\"y\":-482},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":2,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":6,\"name\":null,\"ingressApproach\":null,\"egressApproach\":2,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-988,\"y\":-2151},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":{\"x\":69,\"y\":-329},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":5,\"name\":null,\"ingressApproach\":null,\"egressApproach\":2,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-630,\"y\":-2062},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":{\"x\":76,\"y\":-377},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":4,\"name\":null,\"ingressApproach\":null,\"egressApproach\":2,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-245,\"y\":-2001},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":{\"x\":76,\"y\":-349},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":10,\"name\":null,\"ingressApproach\":null,\"egressApproach\":4,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2374,\"y\":232},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":{\"x\":-357,\"y\":-96},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":8,\"name\":null,\"ingressApproach\":3,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2246,\"y\":-514},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2644,\"y\":-581},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2887,\"y\":-442},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3583,\"y\":-339},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3757,\"y\":27},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3249,\"y\":361},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2050,\"y\":478},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3893,\"y\":1184},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2625,\"y\":766},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2524,\"y\":607},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2280,\"y\":325},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":4,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":7,\"name\":null,\"ingressApproach\":3,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2216,\"y\":-915},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2322,\"y\":-471},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1877,\"y\":-349},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1787,\"y\":-235},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2666,\"y\":-102},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":6,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":null,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":9,\"name\":null,\"ingressApproach\":3,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2295,\"y\":-169},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2420,\"y\":-499},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1968,\"y\":-339},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1843,\"y\":-256},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2121,\"y\":-339},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":4,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":12,\"name\":null,\"ingressApproach\":5,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1364,\"y\":1705},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":-885,\"y\":4854},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-410,\"y\":2559},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-333,\"y\":1847},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-319,\"y\":3244},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":-500,\"y\":4510},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-333,\"y\":3403},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-268,\"y\":2982},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-758,\"y\":11212},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-323,\"y\":11176},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-70}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":5,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":6,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":13,\"name\":null,\"ingressApproach\":5,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-992,\"y\":1735},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":-896,\"y\":4816},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-326,\"y\":2227},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-333,\"y\":2366},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-410,\"y\":3030},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-326,\"y\":3009},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-271,\"y\":2518},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-264,\"y\":2857},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-243,\"y\":2504},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-740,\"y\":11165},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-253,\"y\":11359},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-70}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":4,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":6,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":11,\"name\":null,\"ingressApproach\":5,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1744,\"y\":1607},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-563,\"y\":3136},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-352,\"y\":2336},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-223,\"y\":1407},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-155,\"y\":1778},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":null,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":14,\"name\":null,\"ingressApproach\":null,\"egressApproach\":6,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":398,\"y\":1931},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":{\"x\":-76,\"y\":356},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":15,\"name\":null,\"ingressApproach\":null,\"egressApproach\":6,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":838,\"y\":1985},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":{\"x\":-89,\"y\":349},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null}]}}]},\"roadSegments\":null,\"dataParameters\":null,\"restrictionList\":null},\"dataType\":\"us.dot.its.jpo.ode.plugin.j2735.J2735MAP\"}}"; + String inputMap3 = "{\"metadata\":{\"logFileName\":\"\",\"recordType\":\"mapTx\",\"securityResultCode\":\"success\",\"receivedMessageDetails\":{\"locationData\":null,\"rxSource\":\"NA\"},\"encodings\":null,\"payloadType\":\"us.dot.its.jpo.ode.model.OdeMapPayload\",\"serialId\":{\"streamId\":\"139ba3af-e501-4854-9327-b9e1a2ed8dfb\",\"bundleSize\":1,\"bundleId\":0,\"recordId\":0,\"serialNumber\":0},\"odeReceivedAt\":\"2024-02-22T21:12:48.355Z\",\"schemaVersion\":6,\"maxDurationTime\":0,\"recordGeneratedAt\":\"\",\"recordGeneratedBy\":null,\"sanitized\":false,\"odePacketID\":\"\",\"odeTimStartDateTime\":\"\",\"mapSource\":\"RSU\",\"originIp\":\"10.11.81.12\"},\"payload\":{\"data\":{\"timeStamp\":null,\"msgIssueRevision\":2,\"layerType\":\"intersectionData\",\"layerID\":0,\"intersections\":{\"intersectionGeometry\":[{\"name\":null,\"id\":{\"region\":null,\"id\":12109},\"revision\":2,\"refPoint\":{\"latitude\":39.5880413,\"longitude\":-105.0908854,\"elevation\":1500},\"laneWidth\":366,\"speedLimits\":null,\"laneSet\":{\"GenericLane\":[{\"laneID\":1,\"name\":null,\"ingressApproach\":1,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":1511,\"y\":-1514},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":723,\"y\":-3116},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":892,\"y\":-3818},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":702,\"y\":-2507},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":681,\"y\":-2412},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":827,\"y\":-2798},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1176,\"y\":-3614},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":683,\"y\":-1992},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1000,\"y\":-2818},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":466,\"y\":-1295},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":2505,\"y\":-6164},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":6037,\"y\":-13380},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":70}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":2,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":2,\"name\":null,\"ingressApproach\":1,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":1192,\"y\":-1619},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":807,\"y\":-3733},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":1010,\"y\":-4226},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":1612,\"y\":-5477},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1142,\"y\":-3648},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1131,\"y\":-3343},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":2259,\"y\":-6170},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":2359,\"y\":-5737},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":5300,\"y\":-11774},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":50}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":2,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":3,\"name\":null,\"ingressApproach\":1,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":805,\"y\":-1704},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":380,\"y\":-1813},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":832,\"y\":-3451},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":{\"x\":202,\"y\":-872},\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":{\"x\":340,\"y\":-482},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":2,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":6,\"name\":null,\"ingressApproach\":null,\"egressApproach\":2,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-988,\"y\":-2151},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":{\"x\":69,\"y\":-329},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":5,\"name\":null,\"ingressApproach\":null,\"egressApproach\":2,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-630,\"y\":-2062},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":{\"x\":76,\"y\":-377},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":4,\"name\":null,\"ingressApproach\":null,\"egressApproach\":2,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-245,\"y\":-2001},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":{\"x\":76,\"y\":-349},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":10,\"name\":null,\"ingressApproach\":null,\"egressApproach\":4,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2374,\"y\":232},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":{\"x\":-357,\"y\":-96},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":8,\"name\":null,\"ingressApproach\":3,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2246,\"y\":-514},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2644,\"y\":-581},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2887,\"y\":-442},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3583,\"y\":-339},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3757,\"y\":27},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3249,\"y\":361},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2050,\"y\":478},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3893,\"y\":1184},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2625,\"y\":766},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2524,\"y\":607},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2280,\"y\":325},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":4,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":7,\"name\":null,\"ingressApproach\":3,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2216,\"y\":-915},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2322,\"y\":-471},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1877,\"y\":-349},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1787,\"y\":-235},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2666,\"y\":-102},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":6,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":null,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":9,\"name\":null,\"ingressApproach\":3,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2295,\"y\":-169},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2420,\"y\":-499},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1968,\"y\":-339},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1843,\"y\":-256},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2121,\"y\":-339},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":4,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":12,\"name\":null,\"ingressApproach\":5,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1364,\"y\":1705},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":-885,\"y\":4854},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-410,\"y\":2559},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-333,\"y\":1847},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-319,\"y\":3244},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":-500,\"y\":4510},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-333,\"y\":3403},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-268,\"y\":2982},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-758,\"y\":11212},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-323,\"y\":11176},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-70}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":5,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":6,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":13,\"name\":null,\"ingressApproach\":5,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-992,\"y\":1735},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":-896,\"y\":4816},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-326,\"y\":2227},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-333,\"y\":2366},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-410,\"y\":3030},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-326,\"y\":3009},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-271,\"y\":2518},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-264,\"y\":2857},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-243,\"y\":2504},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-740,\"y\":11165},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-253,\"y\":11359},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-70}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":4,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":6,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":11,\"name\":null,\"ingressApproach\":5,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1744,\"y\":1607},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-563,\"y\":3136},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-352,\"y\":2336},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-223,\"y\":1407},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-155,\"y\":1778},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":null,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":14,\"name\":null,\"ingressApproach\":null,\"egressApproach\":6,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":398,\"y\":1931},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":{\"x\":-76,\"y\":356},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":15,\"name\":null,\"ingressApproach\":null,\"egressApproach\":6,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":838,\"y\":1985},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":{\"x\":-89,\"y\":349},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null}]}}]},\"roadSegments\":null,\"dataParameters\":null,\"restrictionList\":null},\"dataType\":\"us.dot.its.jpo.ode.plugin.j2735.J2735MAP\"}}"; + String inputMap4 = "{\"metadata\":{\"logFileName\":\"\",\"recordType\":\"mapTx\",\"securityResultCode\":\"success\",\"receivedMessageDetails\":{\"locationData\":null,\"rxSource\":\"NA\"},\"encodings\":null,\"payloadType\":\"us.dot.its.jpo.ode.model.OdeMapPayload\",\"serialId\":{\"streamId\":\"139ba3af-e501-4854-9327-b9e1a2ed8dfb\",\"bundleSize\":1,\"bundleId\":0,\"recordId\":0,\"serialNumber\":0},\"odeReceivedAt\":\"2024-02-22T22:12:49.362Z\",\"schemaVersion\":6,\"maxDurationTime\":0,\"recordGeneratedAt\":\"\",\"recordGeneratedBy\":null,\"sanitized\":false,\"odePacketID\":\"\",\"odeTimStartDateTime\":\"\",\"mapSource\":\"RSU\",\"originIp\":\"10.11.81.12\"},\"payload\":{\"data\":{\"timeStamp\":null,\"msgIssueRevision\":2,\"layerType\":\"intersectionData\",\"layerID\":0,\"intersections\":{\"intersectionGeometry\":[{\"name\":null,\"id\":{\"region\":null,\"id\":12109},\"revision\":2,\"refPoint\":{\"latitude\":39.5880413,\"longitude\":-105.0908854,\"elevation\":1500},\"laneWidth\":366,\"speedLimits\":null,\"laneSet\":{\"GenericLane\":[{\"laneID\":1,\"name\":null,\"ingressApproach\":1,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":1511,\"y\":-1514},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":723,\"y\":-3116},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":892,\"y\":-3818},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":702,\"y\":-2507},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":681,\"y\":-2412},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":827,\"y\":-2798},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1176,\"y\":-3614},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":683,\"y\":-1992},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1000,\"y\":-2818},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":466,\"y\":-1295},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":2505,\"y\":-6164},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":6037,\"y\":-13380},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":70}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":2,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":2,\"name\":null,\"ingressApproach\":1,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":1192,\"y\":-1619},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":807,\"y\":-3733},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":1010,\"y\":-4226},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":1612,\"y\":-5477},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1142,\"y\":-3648},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":1131,\"y\":-3343},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":2259,\"y\":-6170},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":2359,\"y\":-5737},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":5300,\"y\":-11774},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":50}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":2,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":3,\"name\":null,\"ingressApproach\":1,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":805,\"y\":-1704},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":380,\"y\":-1813},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":832,\"y\":-3451},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":{\"x\":202,\"y\":-872},\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":{\"x\":340,\"y\":-482},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":2,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":6,\"name\":null,\"ingressApproach\":null,\"egressApproach\":2,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-988,\"y\":-2151},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":20}},{\"delta\":{\"nodeXY1\":{\"x\":69,\"y\":-329},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":5,\"name\":null,\"ingressApproach\":null,\"egressApproach\":2,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-630,\"y\":-2062},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":{\"x\":76,\"y\":-377},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":4,\"name\":null,\"ingressApproach\":null,\"egressApproach\":2,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-245,\"y\":-2001},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":{\"x\":76,\"y\":-349},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":10,\"name\":null,\"ingressApproach\":null,\"egressApproach\":4,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2374,\"y\":232},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":{\"x\":-357,\"y\":-96},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":8,\"name\":null,\"ingressApproach\":3,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2246,\"y\":-514},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2644,\"y\":-581},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2887,\"y\":-442},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3583,\"y\":-339},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3757,\"y\":27},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3249,\"y\":361},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2050,\"y\":478},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-3893,\"y\":1184},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2625,\"y\":766},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2524,\"y\":607},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2280,\"y\":325},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":4,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":7,\"name\":null,\"ingressApproach\":3,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2216,\"y\":-915},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2322,\"y\":-471},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1877,\"y\":-349},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1787,\"y\":-235},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2666,\"y\":-102},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":6,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":null,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":9,\"name\":null,\"ingressApproach\":3,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2295,\"y\":-169},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2420,\"y\":-499},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1968,\"y\":-339},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1843,\"y\":-256},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-2121,\"y\":-339},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":4,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":12,\"name\":null,\"ingressApproach\":5,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1364,\"y\":1705},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":-885,\"y\":4854},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-410,\"y\":2559},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-333,\"y\":1847},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-319,\"y\":3244},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":-500,\"y\":4510},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-333,\"y\":3403},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-268,\"y\":2982},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-758,\"y\":11212},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-323,\"y\":11176},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-70}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":5,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":6,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":13,\"name\":null,\"ingressApproach\":5,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-992,\"y\":1735},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":{\"x\":-896,\"y\":4816},\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-326,\"y\":2227},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-333,\"y\":2366},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-410,\"y\":3030},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-326,\"y\":3009},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-271,\"y\":2518},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-264,\"y\":2857},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-243,\"y\":2504},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-740,\"y\":11165},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-30}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":{\"x\":-253,\"y\":11359},\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-70}}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":4,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":6,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":11,\"name\":null,\"ingressApproach\":5,\"egressApproach\":null,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":true,\"egressPath\":false},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-1744,\"y\":1607},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-563,\"y\":3136},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":{\"x\":-352,\"y\":2336},\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-223,\"y\":1407},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":10}},{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":-155,\"y\":1778},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":{\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"remoteIntersection\":null,\"signalGroup\":null,\"userClass\":null,\"connectionID\":1}]},\"overlays\":null},{\"laneID\":14,\"name\":null,\"ingressApproach\":null,\"egressApproach\":6,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":398,\"y\":1931},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-10}},{\"delta\":{\"nodeXY1\":{\"x\":-76,\"y\":356},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null},{\"laneID\":15,\"name\":null,\"ingressApproach\":null,\"egressApproach\":6,\"laneAttributes\":{\"directionalUse\":{\"ingressPath\":false,\"egressPath\":true},\"shareWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false},\"crosswalk\":null,\"bikeLane\":null,\"sidewalk\":null,\"median\":null,\"striping\":null,\"trackedVehicle\":null,\"parking\":null}},\"maneuvers\":null,\"nodeList\":{\"computed\":null,\"nodes\":{\"NodeXY\":[{\"delta\":{\"nodeXY1\":null,\"nodeXY2\":null,\"nodeXY3\":{\"x\":838,\"y\":1985},\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":{\"localNode\":null,\"disabled\":null,\"enabled\":null,\"data\":null,\"dWidth\":null,\"dElevation\":-20}},{\"delta\":{\"nodeXY1\":{\"x\":-89,\"y\":349},\"nodeXY2\":null,\"nodeXY3\":null,\"nodeXY4\":null,\"nodeXY5\":null,\"nodeXY6\":null,\"nodeLatLon\":null},\"attributes\":null}]}},\"connectsTo\":null,\"overlays\":null}]}}]},\"roadSegments\":null,\"dataParameters\":null,\"restrictionList\":null},\"dataType\":\"us.dot.its.jpo.ode.plugin.j2735.J2735MAP\"}}"; + + @Autowired + DeduplicatorProperties props; + + + + @Test + public void testTopology() { + + props = new DeduplicatorProperties(); + props.setKafkaTopicOdeMapJson(inputTopic); + props.setKafkaTopicDeduplicatedOdeMapJson(outputTopic); + + MapDeduplicatorTopology mapDeduplicatorTopology = new MapDeduplicatorTopology(props, null); + + Topology topology = mapDeduplicatorTopology.buildTopology(); + + try (TopologyTestDriver driver = new TopologyTestDriver(topology)) { + + + TestInputTopic inputOdeMapData = driver.createInputTopic( + inputTopic, + Serdes.Void().serializer(), + Serdes.String().serializer()); + + + TestOutputTopic outputOdeMapData = driver.createOutputTopic( + outputTopic, + Serdes.String().deserializer(), + JsonSerdes.OdeMap().deserializer()); + + inputOdeMapData.pipeInput(null, inputMap1); + inputOdeMapData.pipeInput(null, inputMap2); + inputOdeMapData.pipeInput(null, inputMap3); + inputOdeMapData.pipeInput(null, inputMap4); + + List> mapDeduplicationResults = outputOdeMapData.readKeyValuesToList(); + + // validate that only 3 messages make it through + assertEquals(3, mapDeduplicationResults.size()); + + objectMapper = new ObjectMapper(); + OdeMapData map1 = objectMapper.readValue(inputMap1, OdeMapData.class); + OdeMapData map3 = objectMapper.readValue(inputMap3, OdeMapData.class); + OdeMapData map4 = objectMapper.readValue(inputMap4, OdeMapData.class); + + + assertEquals(map1.getMetadata().getOdeReceivedAt(), mapDeduplicationResults.get(0).value.getMetadata().getOdeReceivedAt()); + assertEquals(map3.getMetadata().getOdeReceivedAt(), mapDeduplicationResults.get(1).value.getMetadata().getOdeReceivedAt()); + assertEquals(map4.getMetadata().getOdeReceivedAt(), mapDeduplicationResults.get(2).value.getMetadata().getOdeReceivedAt()); + + } catch (JsonMappingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (JsonProcessingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/OdeRawEncodedTimDeduplicatorTopologyTest.java b/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/OdeRawEncodedTimDeduplicatorTopologyTest.java new file mode 100644 index 0000000..4b618d9 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/OdeRawEncodedTimDeduplicatorTopologyTest.java @@ -0,0 +1,89 @@ +package deduplicator; + +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.TestInputTopic; +import org.apache.kafka.streams.TestOutputTopic; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.TopologyTestDriver; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.topologies.OdeRawEncodedTimDeduplicatorTopology; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + + +public class OdeRawEncodedTimDeduplicatorTopologyTest { + + String inputTopic = "topic.OdeRawEncodedTIMJson"; + String outputTopic = "topic.DeduplicatedOdeRawEncodedTIMJson"; + + ObjectMapper objectMapper = new ObjectMapper(); + + // Original Message + String inputTim1 = "{ \"metadata\": { \"securityResultCode\": \"success\", \"recordGeneratedBy\": \"RSU\", \"schemaVersion\": 6, \"payloadType\": \"us.dot.its.jpo.ode.model.OdeAsn1Payload\", \"serialId\": { \"recordId\": 0, \"serialNumber\": 0, \"streamId\": \"5f470323-7770-4810-b225-8c45aa672103\", \"bundleSize\": 1, \"bundleId\": 0 }, \"sanitized\": false, \"recordType\": \"timMsg\", \"maxDurationTime\": 0, \"odeReceivedAt\": \"2024-07-22T23:23:29.553Z\", \"originIp\": \"10.16.28.53\" }, \"payload\": { \"data\": { \"bytes\": \"001F63701409FF38D05CD47AF567A4570F775D9B0301C269D16DD9656F9637FFF93F421D3B001EA007F99937E1CF5AD1BB0BF4A9D5BEC5BB25CC5B2E64E173162DA00000000269D16DD9656F9631388C100021000EBF7272441F8CFDED60004008027BBAECD8A1A81EF1C153853DD08B394DDCE85F7F4F2222BE087C98000801004F775D9B002F378A81FD1358540F893502C0B711D815FF7883E0A9AACF804536FC9E2A39A67D4289155859ABA8ACBD4997855D345BA429991568E1CA702BA1D8ADFF4805456C0A46862B7F41BE614A122DFB8B0FA019FC52FC8AFF62A7DA3D3F1C9A00BC220AA6B0DC20571A23C142A8C0368F14D181D9B8E2FA859714531606FF8197DC2D81469C97540A33D8D25850D7CA7E82916C1F82142D3A8A20A4884311C320C8EB2290AD1BAC0C84856E8A1B1D760C51BE458B8189591FF0C64E68D0004008027BBAECD8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\" }, \"dataType\": \"us.dot.its.jpo.ode.model.OdeHexByteArray\" }}"; + + // Shifted Forward .1 seconds - Should be deduplicated + String inputTim2 = "{ \"metadata\": { \"securityResultCode\": \"success\", \"recordGeneratedBy\": \"RSU\", \"schemaVersion\": 6, \"payloadType\": \"us.dot.its.jpo.ode.model.OdeAsn1Payload\", \"serialId\": { \"recordId\": 0, \"serialNumber\": 0, \"streamId\": \"5f470323-7770-4810-b225-8c45aa672103\", \"bundleSize\": 1, \"bundleId\": 0 }, \"sanitized\": false, \"recordType\": \"timMsg\", \"maxDurationTime\": 0, \"odeReceivedAt\": \"2024-07-22T23:23:29.653Z\", \"originIp\": \"10.16.28.53\" }, \"payload\": { \"data\": { \"bytes\": \"001F63701409FF38D05CD47AF567A4570F775D9B0301C269D16DD9656F9637FFF93F421D3B001EA007F99937E1CF5AD1BB0BF4A9D5BEC5BB25CC5B2E64E173162DA00000000269D16DD9656F9631388C100021000EBF7272441F8CFDED60004008027BBAECD8A1A81EF1C153853DD08B394DDCE85F7F4F2222BE087C98000801004F775D9B002F378A81FD1358540F893502C0B711D815FF7883E0A9AACF804536FC9E2A39A67D4289155859ABA8ACBD4997855D345BA429991568E1CA702BA1D8ADFF4805456C0A46862B7F41BE614A122DFB8B0FA019FC52FC8AFF62A7DA3D3F1C9A00BC220AA6B0DC20571A23C142A8C0368F14D181D9B8E2FA859714531606FF8197DC2D81469C97540A33D8D25850D7CA7E82916C1F82142D3A8A20A4884311C320C8EB2290AD1BAC0C84856E8A1B1D760C51BE458B8189591FF0C64E68D0004008027BBAECD8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\" }, \"dataType\": \"us.dot.its.jpo.ode.model.OdeHexByteArray\" }}"; + + // Shifted Forward 1 hour Should be allowed to pass through + String inputTim3 = "{ \"metadata\": { \"securityResultCode\": \"success\", \"recordGeneratedBy\": \"RSU\", \"schemaVersion\": 6, \"payloadType\": \"us.dot.its.jpo.ode.model.OdeAsn1Payload\", \"serialId\": { \"recordId\": 0, \"serialNumber\": 0, \"streamId\": \"5f470323-7770-4810-b225-8c45aa672103\", \"bundleSize\": 1, \"bundleId\": 0 }, \"sanitized\": false, \"recordType\": \"timMsg\", \"maxDurationTime\": 0, \"odeReceivedAt\": \"2024-07-23T00:23:29.653Z\", \"originIp\": \"10.16.28.53\" }, \"payload\": { \"data\": { \"bytes\": \"001F63701409FF38D05CD47AF567A4570F775D9B0301C269D16DD9656F9637FFF93F421D3B001EA007F99937E1CF5AD1BB0BF4A9D5BEC5BB25CC5B2E64E173162DA00000000269D16DD9656F9631388C100021000EBF7272441F8CFDED60004008027BBAECD8A1A81EF1C153853DD08B394DDCE85F7F4F2222BE087C98000801004F775D9B002F378A81FD1358540F893502C0B711D815FF7883E0A9AACF804536FC9E2A39A67D4289155859ABA8ACBD4997855D345BA429991568E1CA702BA1D8ADFF4805456C0A46862B7F41BE614A122DFB8B0FA019FC52FC8AFF62A7DA3D3F1C9A00BC220AA6B0DC20571A23C142A8C0368F14D181D9B8E2FA859714531606FF8197DC2D81469C97540A33D8D25850D7CA7E82916C1F82142D3A8A20A4884311C320C8EB2290AD1BAC0C84856E8A1B1D760C51BE458B8189591FF0C64E68D0004008027BBAECD8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\" }, \"dataType\": \"us.dot.its.jpo.ode.model.OdeHexByteArray\" }}"; + + // Has a different payload ID. Should be allowed through + String inputTim4 = "{ \"metadata\": { \"securityResultCode\": \"success\", \"recordGeneratedBy\": \"RSU\", \"schemaVersion\": 6, \"payloadType\": \"us.dot.its.jpo.ode.model.OdeAsn1Payload\", \"serialId\": { \"recordId\": 0, \"serialNumber\": 0, \"streamId\": \"5f470323-7770-4810-b225-8c45aa672103\", \"bundleSize\": 1, \"bundleId\": 0 }, \"sanitized\": false, \"recordType\": \"timMsg\", \"maxDurationTime\": 0, \"odeReceivedAt\": \"2024-07-22T23:23:29.553Z\", \"originIp\": \"10.16.28.53\" }, \"payload\": { \"data\": { \"bytes\": \"001F63701409FF38D05CD47AF567A4570F775D9B0301C269D16DD9656F9637FFF93F421D3B001EA007F99937E1CF5AD1BB0BF4A9D5BEC5BB25CC5B2E64E173162DA00000000269D16DD9656F9631388C100021000EBF7272441F8CFDED60004008027BBAECD8A1A81EF1C153853DD08B394DDCE85F7F4F2222BE087C98000801004F775D9B002F378A81FD1358540F893502C0B711D815FF7883E0A9AACF804536FC9E2A39A67D4289155859ABA8ACBD4997855D345BA429991568E1CA702BA1D8ADFF4805456C0A46862B7F41BE614A122DFB8B0FA019FC52FC8AFF62A7DA3D3F1C9A00BC220AA6B0DC20571A23C142A8C0368F14D181D9B8E2FA859714531606FF8197DC2D81469C97540A33D8D25850D7CA7E82916C1F82142D3A8A20A4884311C320C8EB2290AD1BAC0C84856E8A1B1D760C51BE458B8189591FF0C64E68D0004008027BBAECD9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\" }, \"dataType\": \"us.dot.its.jpo.ode.model.OdeHexByteArray\" }}"; + + @Autowired + DeduplicatorProperties props; + + @Test + public void testTopology() { + + props = new DeduplicatorProperties(); + props.setKafkaTopicOdeRawEncodedTimJson(inputTopic); + props.setKafkaTopicDeduplicatedOdeRawEncodedTimJson(outputTopic); + + OdeRawEncodedTimDeduplicatorTopology TimDeduplicatorTopology = new OdeRawEncodedTimDeduplicatorTopology(props, null); + + Topology topology = TimDeduplicatorTopology.buildTopology(); + objectMapper.registerModule(new JavaTimeModule()); + + try (TopologyTestDriver driver = new TopologyTestDriver(topology)) { + + + TestInputTopic inputTimData = driver.createInputTopic( + inputTopic, + Serdes.Void().serializer(), + Serdes.String().serializer()); + + + TestOutputTopic outputTimData = driver.createOutputTopic( + outputTopic, + Serdes.String().deserializer(), + Serdes.String().deserializer()); + + inputTimData.pipeInput(null, inputTim1); + inputTimData.pipeInput(null, inputTim2); + inputTimData.pipeInput(null, inputTim3); + inputTimData.pipeInput(null, inputTim4); + + List> timDeduplicatedResults = outputTimData.readKeyValuesToList(); + + // validate that only 3 messages make it through + assertEquals(3, timDeduplicatedResults.size()); + inputTim1 = inputTim1.strip(); + + assertEquals(inputTim1.replace(" ", ""), timDeduplicatedResults.get(0).value.replace(" ", "")); + assertEquals(inputTim3.replace(" ", ""), timDeduplicatedResults.get(1).value.replace(" ", "")); + assertEquals(inputTim4.replace(" ", ""), timDeduplicatedResults.get(2).value.replace(" ", "")); + + }catch(Exception e){ + e.printStackTrace(); + } + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/ProcessedMapDeduplicatorTopologyTest.java b/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/ProcessedMapDeduplicatorTopologyTest.java new file mode 100644 index 0000000..331bfdc --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/ProcessedMapDeduplicatorTopologyTest.java @@ -0,0 +1,99 @@ +package deduplicator; + +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.TestInputTopic; +import org.apache.kafka.streams.TestOutputTopic; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.TopologyTestDriver; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.topologies.ProcessedMapDeduplicatorTopology; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.LineString; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.map.ProcessedMap; +import us.dot.its.jpo.geojsonconverter.serialization.JsonSerdes; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + + +public class ProcessedMapDeduplicatorTopologyTest { + + String inputTopic = "topic.ProcessedMap"; + String outputTopic = "topic.DeduplicatedProcessedMap"; + + + TypeReference> typeReference = new TypeReference<>(){}; + ObjectMapper objectMapper = new ObjectMapper(); + + String inputProcessedMap1 = "{\"mapFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":1,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907089,39.587905],[-105.0906245,39.5876246],[-105.0905203,39.587281],[-105.0904383,39.5870554],[-105.0903588,39.5868383],[-105.0902622,39.5865865],[-105.0901249,39.5862612],[-105.0900451,39.5860819],[-105.0899283,39.5858283],[-105.0898739,39.5857117],[-105.0895814,39.5851569],[-105.0888764,39.5839527]]},\"properties\":{\"nodes\":[{\"delta\":[1511,-1514]},{\"delta\":[723,-3116],\"delevation\":10},{\"delta\":[892,-3818],\"delevation\":20},{\"delta\":[702,-2507],\"delevation\":20},{\"delta\":[681,-2412],\"delevation\":10},{\"delta\":[827,-2798],\"delevation\":10},{\"delta\":[1176,-3614],\"delevation\":20},{\"delta\":[683,-1992]},{\"delta\":[1000,-2818],\"delevation\":10},{\"delta\":[466,-1295],\"delevation\":20},{\"delta\":[2505,-6164],\"delevation\":20},{\"delta\":[6037,-13380],\"delevation\":70}],\"laneId\":1,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":2,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907462,39.5878956],[-105.090652,39.5875596],[-105.090534,39.5871793],[-105.0903457,39.5866864],[-105.0902123,39.5863581],[-105.0900802,39.5860572],[-105.0898164,39.5855019],[-105.0895409,39.5849856],[-105.088922,39.5839259]]},\"properties\":{\"nodes\":[{\"delta\":[1192,-1619]},{\"delta\":[807,-3733],\"delevation\":30},{\"delta\":[1010,-4226],\"delevation\":10},{\"delta\":[1612,-5477],\"delevation\":30},{\"delta\":[1142,-3648],\"delevation\":20},{\"delta\":[1131,-3343],\"delevation\":10},{\"delta\":[2259,-6170],\"delevation\":30},{\"delta\":[2359,-5737],\"delevation\":30},{\"delta\":[5300,-11774],\"delevation\":50}],\"laneId\":2,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":3,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907914,39.5878879],[-105.090747,39.5877247],[-105.0906498,39.5874141],[-105.0906262,39.5873356],[-105.0905865,39.5872922]]},\"properties\":{\"nodes\":[{\"delta\":[805,-1704],\"delevation\":10},{\"delta\":[380,-1813]},{\"delta\":[832,-3451],\"delevation\":30},{\"delta\":[202,-872]},{\"delta\":[340,-482],\"delevation\":-10}],\"laneId\":3,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":6,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910008,39.5878477],[-105.0909927,39.5878181]]},\"properties\":{\"nodes\":[{\"delta\":[-988,-2151],\"delevation\":20},{\"delta\":[69,-329]}],\"laneId\":6,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":5,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.090959,39.5878557],[-105.0909501,39.5878218]]},\"properties\":{\"nodes\":[{\"delta\":[-630,-2062],\"delevation\":10},{\"delta\":[76,-377],\"delevation\":10}],\"laneId\":5,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":4,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.090914,39.5878612],[-105.0909051,39.5878298]]},\"properties\":{\"nodes\":[{\"delta\":[-245,-2001],\"delevation\":10},{\"delta\":[76,-349]}],\"laneId\":4,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":10,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911626,39.5880622],[-105.0912043,39.5880536]]},\"properties\":{\"nodes\":[{\"delta\":[-2374,232],\"delevation\":10},{\"delta\":[-357,-96]}],\"laneId\":10,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":4,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":8,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911477,39.587995],[-105.0914565,39.5879427],[-105.0917937,39.5879029],[-105.0922121,39.5878724],[-105.0926509,39.5878748],[-105.0930303,39.5879073],[-105.0932697,39.5879503],[-105.0937243,39.5880569],[-105.0940309,39.5881258],[-105.0943257,39.5881804],[-105.094592,39.5882097]]},\"properties\":{\"nodes\":[{\"delta\":[-2246,-514],\"delevation\":10},{\"delta\":[-2644,-581]},{\"delta\":[-2887,-442],\"delevation\":10},{\"delta\":[-3583,-339],\"delevation\":10},{\"delta\":[-3757,27]},{\"delta\":[-3249,361],\"delevation\":-10},{\"delta\":[-2050,478]},{\"delta\":[-3893,1184]},{\"delta\":[-2625,766],\"delevation\":-10},{\"delta\":[-2524,607],\"delevation\":10},{\"delta\":[-2280,325],\"delevation\":10}],\"laneId\":8,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":7,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911442,39.5879589],[-105.0914154,39.5879165],[-105.0916346,39.5878851],[-105.0918433,39.5878639],[-105.0921546,39.5878547]]},\"properties\":{\"nodes\":[{\"delta\":[-2216,-915],\"delevation\":10},{\"delta\":[-2322,-471]},{\"delta\":[-1877,-349],\"delevation\":10},{\"delta\":[-1787,-235]},{\"delta\":[-2666,-102],\"delevation\":10}],\"laneId\":7,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":6,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":9,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911534,39.5880261],[-105.091436,39.5879812],[-105.0916658,39.5879507],[-105.091881,39.5879277],[-105.0921287,39.5878972]]},\"properties\":{\"nodes\":[{\"delta\":[-2295,-169],\"delevation\":10},{\"delta\":[-2420,-499]},{\"delta\":[-1968,-339],\"delevation\":10},{\"delta\":[-1843,-256]},{\"delta\":[-2121,-339]}],\"laneId\":9,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":12,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910447,39.5881948],[-105.0911481,39.5886317],[-105.091196,39.588862],[-105.0912349,39.5890282],[-105.0912722,39.5893202],[-105.0913306,39.5897261],[-105.0913695,39.5900324],[-105.0914008,39.5903008],[-105.0914893,39.5913099],[-105.091527,39.5923157]]},\"properties\":{\"nodes\":[{\"delta\":[-1364,1705],\"delevation\":10},{\"delta\":[-885,4854],\"delevation\":-30},{\"delta\":[-410,2559],\"delevation\":10},{\"delta\":[-333,1847],\"delevation\":-10},{\"delta\":[-319,3244],\"delevation\":-20},{\"delta\":[-500,4510]},{\"delta\":[-333,3403],\"delevation\":-30},{\"delta\":[-268,2982]},{\"delta\":[-758,11212],\"delevation\":-30},{\"delta\":[-323,11176],\"delevation\":-70}],\"laneId\":12,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":5,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":13,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910013,39.5881975],[-105.0911059,39.5886309],[-105.091144,39.5888313],[-105.0911829,39.5890442],[-105.0912308,39.5893169],[-105.0912689,39.5895877],[-105.0913005,39.5898143],[-105.0913313,39.5900714],[-105.0913597,39.5902968],[-105.0914461,39.5913017],[-105.0914756,39.592324]]},\"properties\":{\"nodes\":[{\"delta\":[-992,1735],\"delevation\":10},{\"delta\":[-896,4816],\"delevation\":-30},{\"delta\":[-326,2227],\"delevation\":10},{\"delta\":[-333,2366]},{\"delta\":[-410,3030],\"delevation\":-20},{\"delta\":[-326,3009],\"delevation\":-10},{\"delta\":[-271,2518],\"delevation\":-10},{\"delta\":[-264,2857],\"delevation\":-20},{\"delta\":[-243,2504]},{\"delta\":[-740,11165],\"delevation\":-30},{\"delta\":[-253,11359],\"delevation\":-70}],\"laneId\":13,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":4,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":11,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910891,39.5881859],[-105.0911549,39.5884681],[-105.091196,39.5886783],[-105.091222,39.5888049],[-105.0912401,39.5889649]]},\"properties\":{\"nodes\":[{\"delta\":[-1744,1607],\"delevation\":10},{\"delta\":[-563,3136],\"delevation\":-20},{\"delta\":[-352,2336],\"delevation\":-10},{\"delta\":[-223,1407],\"delevation\":10},{\"delta\":[-155,1778]}],\"laneId\":11,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":14,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0908389,39.5882151],[-105.0908478,39.5882471]]},\"properties\":{\"nodes\":[{\"delta\":[398,1931],\"delevation\":-10},{\"delta\":[-76,356]}],\"laneId\":14,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":15,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907875,39.58822],[-105.0907979,39.5882514]]},\"properties\":{\"nodes\":[{\"delta\":[838,1985],\"delevation\":-20},{\"delta\":[-89,349]}],\"laneId\":15,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}}]},\"connectingLanesFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":\"1-15\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907089,39.587905],[-105.0907875,39.58822]]},\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":1,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"2-14\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907462,39.5878956],[-105.0908389,39.5882151]]},\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":2,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"3-10\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907914,39.5878879],[-105.0911626,39.5880622]]},\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":3,\"egressLaneId\":10}},{\"type\":\"Feature\",\"id\":\"8-15\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911477,39.587995],[-105.0907875,39.58822]]},\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":8,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"7-6\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911442,39.5879589],[-105.0910008,39.5878477]]},\"properties\":{\"ingressLaneId\":7,\"egressLaneId\":6}},{\"type\":\"Feature\",\"id\":\"9-14\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911534,39.5880261],[-105.0908389,39.5882151]]},\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":9,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"12-5\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910447,39.5881948],[-105.090959,39.5878557]]},\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":12,\"egressLaneId\":5}},{\"type\":\"Feature\",\"id\":\"13-4\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910013,39.5881975],[-105.090914,39.5878612]]},\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":13,\"egressLaneId\":4}},{\"type\":\"Feature\",\"id\":\"11-10\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910891,39.5881859],[-105.0911626,39.5880622]]},\"properties\":{\"ingressLaneId\":11,\"egressLaneId\":10}}]},\"properties\":{\"messageType\":\"MAP\",\"odeReceivedAt\":\"2024-02-22T23:26:21.06Z\",\"originIp\":\"10.11.81.12\",\"intersectionId\":12109,\"msgIssueRevision\":2,\"revision\":2,\"refPoint\":{\"latitude\":39.5880413,\"longitude\":-105.0908854,\"elevation\":1691},\"cti4501Conformant\":false,\"validationMessages\":[{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].id.region: null found, integer expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].id.region\",\"schemaPath\":\"#/$defs/J2735RoadRegulatorID/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[8].connectsTo.connectsTo[0].signalGroup: null found, integer expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[8].connectsTo.connectsTo[0].signalGroup\",\"schemaPath\":\"#/$defs/J2735SignalGroupID/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[12].connectsTo.connectsTo[0].signalGroup: null found, integer expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[12].connectsTo.connectsTo[0].signalGroup\",\"schemaPath\":\"#/$defs/J2735SignalGroupID/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].speedLimits: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].speedLimits\",\"schemaPath\":\"#/$defs/J2735SpeedLimitList_Wrapper/type\"}],\"laneWidth\":366,\"mapSource\":\"RSU\",\"timeStamp\":\"2024-02-22T23:26:21.06Z\"}}"; + String inputProcessedMap2 = "{\"mapFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":1,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907089,39.587905],[-105.0906245,39.5876246],[-105.0905203,39.587281],[-105.0904383,39.5870554],[-105.0903588,39.5868383],[-105.0902622,39.5865865],[-105.0901249,39.5862612],[-105.0900451,39.5860819],[-105.0899283,39.5858283],[-105.0898739,39.5857117],[-105.0895814,39.5851569],[-105.0888764,39.5839527]]},\"properties\":{\"nodes\":[{\"delta\":[1511,-1514]},{\"delta\":[723,-3116],\"delevation\":10},{\"delta\":[892,-3818],\"delevation\":20},{\"delta\":[702,-2507],\"delevation\":20},{\"delta\":[681,-2412],\"delevation\":10},{\"delta\":[827,-2798],\"delevation\":10},{\"delta\":[1176,-3614],\"delevation\":20},{\"delta\":[683,-1992]},{\"delta\":[1000,-2818],\"delevation\":10},{\"delta\":[466,-1295],\"delevation\":20},{\"delta\":[2505,-6164],\"delevation\":20},{\"delta\":[6037,-13380],\"delevation\":70}],\"laneId\":1,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":2,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907462,39.5878956],[-105.090652,39.5875596],[-105.090534,39.5871793],[-105.0903457,39.5866864],[-105.0902123,39.5863581],[-105.0900802,39.5860572],[-105.0898164,39.5855019],[-105.0895409,39.5849856],[-105.088922,39.5839259]]},\"properties\":{\"nodes\":[{\"delta\":[1192,-1619]},{\"delta\":[807,-3733],\"delevation\":30},{\"delta\":[1010,-4226],\"delevation\":10},{\"delta\":[1612,-5477],\"delevation\":30},{\"delta\":[1142,-3648],\"delevation\":20},{\"delta\":[1131,-3343],\"delevation\":10},{\"delta\":[2259,-6170],\"delevation\":30},{\"delta\":[2359,-5737],\"delevation\":30},{\"delta\":[5300,-11774],\"delevation\":50}],\"laneId\":2,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":3,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907914,39.5878879],[-105.090747,39.5877247],[-105.0906498,39.5874141],[-105.0906262,39.5873356],[-105.0905865,39.5872922]]},\"properties\":{\"nodes\":[{\"delta\":[805,-1704],\"delevation\":10},{\"delta\":[380,-1813]},{\"delta\":[832,-3451],\"delevation\":30},{\"delta\":[202,-872]},{\"delta\":[340,-482],\"delevation\":-10}],\"laneId\":3,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":6,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910008,39.5878477],[-105.0909927,39.5878181]]},\"properties\":{\"nodes\":[{\"delta\":[-988,-2151],\"delevation\":20},{\"delta\":[69,-329]}],\"laneId\":6,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":5,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.090959,39.5878557],[-105.0909501,39.5878218]]},\"properties\":{\"nodes\":[{\"delta\":[-630,-2062],\"delevation\":10},{\"delta\":[76,-377],\"delevation\":10}],\"laneId\":5,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":4,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.090914,39.5878612],[-105.0909051,39.5878298]]},\"properties\":{\"nodes\":[{\"delta\":[-245,-2001],\"delevation\":10},{\"delta\":[76,-349]}],\"laneId\":4,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":10,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911626,39.5880622],[-105.0912043,39.5880536]]},\"properties\":{\"nodes\":[{\"delta\":[-2374,232],\"delevation\":10},{\"delta\":[-357,-96]}],\"laneId\":10,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":4,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":8,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911477,39.587995],[-105.0914565,39.5879427],[-105.0917937,39.5879029],[-105.0922121,39.5878724],[-105.0926509,39.5878748],[-105.0930303,39.5879073],[-105.0932697,39.5879503],[-105.0937243,39.5880569],[-105.0940309,39.5881258],[-105.0943257,39.5881804],[-105.094592,39.5882097]]},\"properties\":{\"nodes\":[{\"delta\":[-2246,-514],\"delevation\":10},{\"delta\":[-2644,-581]},{\"delta\":[-2887,-442],\"delevation\":10},{\"delta\":[-3583,-339],\"delevation\":10},{\"delta\":[-3757,27]},{\"delta\":[-3249,361],\"delevation\":-10},{\"delta\":[-2050,478]},{\"delta\":[-3893,1184]},{\"delta\":[-2625,766],\"delevation\":-10},{\"delta\":[-2524,607],\"delevation\":10},{\"delta\":[-2280,325],\"delevation\":10}],\"laneId\":8,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":7,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911442,39.5879589],[-105.0914154,39.5879165],[-105.0916346,39.5878851],[-105.0918433,39.5878639],[-105.0921546,39.5878547]]},\"properties\":{\"nodes\":[{\"delta\":[-2216,-915],\"delevation\":10},{\"delta\":[-2322,-471]},{\"delta\":[-1877,-349],\"delevation\":10},{\"delta\":[-1787,-235]},{\"delta\":[-2666,-102],\"delevation\":10}],\"laneId\":7,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":6,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":9,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911534,39.5880261],[-105.091436,39.5879812],[-105.0916658,39.5879507],[-105.091881,39.5879277],[-105.0921287,39.5878972]]},\"properties\":{\"nodes\":[{\"delta\":[-2295,-169],\"delevation\":10},{\"delta\":[-2420,-499]},{\"delta\":[-1968,-339],\"delevation\":10},{\"delta\":[-1843,-256]},{\"delta\":[-2121,-339]}],\"laneId\":9,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":12,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910447,39.5881948],[-105.0911481,39.5886317],[-105.091196,39.588862],[-105.0912349,39.5890282],[-105.0912722,39.5893202],[-105.0913306,39.5897261],[-105.0913695,39.5900324],[-105.0914008,39.5903008],[-105.0914893,39.5913099],[-105.091527,39.5923157]]},\"properties\":{\"nodes\":[{\"delta\":[-1364,1705],\"delevation\":10},{\"delta\":[-885,4854],\"delevation\":-30},{\"delta\":[-410,2559],\"delevation\":10},{\"delta\":[-333,1847],\"delevation\":-10},{\"delta\":[-319,3244],\"delevation\":-20},{\"delta\":[-500,4510]},{\"delta\":[-333,3403],\"delevation\":-30},{\"delta\":[-268,2982]},{\"delta\":[-758,11212],\"delevation\":-30},{\"delta\":[-323,11176],\"delevation\":-70}],\"laneId\":12,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":5,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":13,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910013,39.5881975],[-105.0911059,39.5886309],[-105.091144,39.5888313],[-105.0911829,39.5890442],[-105.0912308,39.5893169],[-105.0912689,39.5895877],[-105.0913005,39.5898143],[-105.0913313,39.5900714],[-105.0913597,39.5902968],[-105.0914461,39.5913017],[-105.0914756,39.592324]]},\"properties\":{\"nodes\":[{\"delta\":[-992,1735],\"delevation\":10},{\"delta\":[-896,4816],\"delevation\":-30},{\"delta\":[-326,2227],\"delevation\":10},{\"delta\":[-333,2366]},{\"delta\":[-410,3030],\"delevation\":-20},{\"delta\":[-326,3009],\"delevation\":-10},{\"delta\":[-271,2518],\"delevation\":-10},{\"delta\":[-264,2857],\"delevation\":-20},{\"delta\":[-243,2504]},{\"delta\":[-740,11165],\"delevation\":-30},{\"delta\":[-253,11359],\"delevation\":-70}],\"laneId\":13,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":4,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":11,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910891,39.5881859],[-105.0911549,39.5884681],[-105.091196,39.5886783],[-105.091222,39.5888049],[-105.0912401,39.5889649]]},\"properties\":{\"nodes\":[{\"delta\":[-1744,1607],\"delevation\":10},{\"delta\":[-563,3136],\"delevation\":-20},{\"delta\":[-352,2336],\"delevation\":-10},{\"delta\":[-223,1407],\"delevation\":10},{\"delta\":[-155,1778]}],\"laneId\":11,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":14,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0908389,39.5882151],[-105.0908478,39.5882471]]},\"properties\":{\"nodes\":[{\"delta\":[398,1931],\"delevation\":-10},{\"delta\":[-76,356]}],\"laneId\":14,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":15,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907875,39.58822],[-105.0907979,39.5882514]]},\"properties\":{\"nodes\":[{\"delta\":[838,1985],\"delevation\":-20},{\"delta\":[-89,349]}],\"laneId\":15,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}}]},\"connectingLanesFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":\"1-15\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907089,39.587905],[-105.0907875,39.58822]]},\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":1,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"2-14\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907462,39.5878956],[-105.0908389,39.5882151]]},\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":2,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"3-10\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907914,39.5878879],[-105.0911626,39.5880622]]},\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":3,\"egressLaneId\":10}},{\"type\":\"Feature\",\"id\":\"8-15\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911477,39.587995],[-105.0907875,39.58822]]},\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":8,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"7-6\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911442,39.5879589],[-105.0910008,39.5878477]]},\"properties\":{\"ingressLaneId\":7,\"egressLaneId\":6}},{\"type\":\"Feature\",\"id\":\"9-14\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911534,39.5880261],[-105.0908389,39.5882151]]},\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":9,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"12-5\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910447,39.5881948],[-105.090959,39.5878557]]},\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":12,\"egressLaneId\":5}},{\"type\":\"Feature\",\"id\":\"13-4\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910013,39.5881975],[-105.090914,39.5878612]]},\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":13,\"egressLaneId\":4}},{\"type\":\"Feature\",\"id\":\"11-10\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910891,39.5881859],[-105.0911626,39.5880622]]},\"properties\":{\"ingressLaneId\":11,\"egressLaneId\":10}}]},\"properties\":{\"messageType\":\"MAP\",\"odeReceivedAt\":\"2024-02-22T23:26:22.074Z\",\"originIp\":\"10.11.81.12\",\"intersectionId\":12109,\"msgIssueRevision\":2,\"revision\":2,\"refPoint\":{\"latitude\":39.5880413,\"longitude\":-105.0908854,\"elevation\":1691},\"cti4501Conformant\":false,\"validationMessages\":[{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].id.region: null found, integer expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].id.region\",\"schemaPath\":\"#/$defs/J2735RoadRegulatorID/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[8].connectsTo.connectsTo[0].signalGroup: null found, integer expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[8].connectsTo.connectsTo[0].signalGroup\",\"schemaPath\":\"#/$defs/J2735SignalGroupID/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[12].connectsTo.connectsTo[0].signalGroup: null found, integer expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[12].connectsTo.connectsTo[0].signalGroup\",\"schemaPath\":\"#/$defs/J2735SignalGroupID/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].speedLimits: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].speedLimits\",\"schemaPath\":\"#/$defs/J2735SpeedLimitList_Wrapper/type\"}],\"laneWidth\":366,\"mapSource\":\"RSU\",\"timeStamp\":\"2024-02-22T23:26:22.074Z\"}}"; + String inputProcessedMap3 = "{\"mapFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":1,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907089,39.587905],[-105.0906245,39.5876246],[-105.0905203,39.587281],[-105.0904383,39.5870554],[-105.0903588,39.5868383],[-105.0902622,39.5865865],[-105.0901249,39.5862612],[-105.0900451,39.5860819],[-105.0899283,39.5858283],[-105.0898739,39.5857117],[-105.0895814,39.5851569],[-105.0888764,39.5839527]]},\"properties\":{\"nodes\":[{\"delta\":[1511,-1514]},{\"delta\":[723,-3116],\"delevation\":10},{\"delta\":[892,-3818],\"delevation\":20},{\"delta\":[702,-2507],\"delevation\":20},{\"delta\":[681,-2412],\"delevation\":10},{\"delta\":[827,-2798],\"delevation\":10},{\"delta\":[1176,-3614],\"delevation\":20},{\"delta\":[683,-1992]},{\"delta\":[1000,-2818],\"delevation\":10},{\"delta\":[466,-1295],\"delevation\":20},{\"delta\":[2505,-6164],\"delevation\":20},{\"delta\":[6037,-13380],\"delevation\":70}],\"laneId\":1,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":2,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907462,39.5878956],[-105.090652,39.5875596],[-105.090534,39.5871793],[-105.0903457,39.5866864],[-105.0902123,39.5863581],[-105.0900802,39.5860572],[-105.0898164,39.5855019],[-105.0895409,39.5849856],[-105.088922,39.5839259]]},\"properties\":{\"nodes\":[{\"delta\":[1192,-1619]},{\"delta\":[807,-3733],\"delevation\":30},{\"delta\":[1010,-4226],\"delevation\":10},{\"delta\":[1612,-5477],\"delevation\":30},{\"delta\":[1142,-3648],\"delevation\":20},{\"delta\":[1131,-3343],\"delevation\":10},{\"delta\":[2259,-6170],\"delevation\":30},{\"delta\":[2359,-5737],\"delevation\":30},{\"delta\":[5300,-11774],\"delevation\":50}],\"laneId\":2,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":3,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907914,39.5878879],[-105.090747,39.5877247],[-105.0906498,39.5874141],[-105.0906262,39.5873356],[-105.0905865,39.5872922]]},\"properties\":{\"nodes\":[{\"delta\":[805,-1704],\"delevation\":10},{\"delta\":[380,-1813]},{\"delta\":[832,-3451],\"delevation\":30},{\"delta\":[202,-872]},{\"delta\":[340,-482],\"delevation\":-10}],\"laneId\":3,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":6,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910008,39.5878477],[-105.0909927,39.5878181]]},\"properties\":{\"nodes\":[{\"delta\":[-988,-2151],\"delevation\":20},{\"delta\":[69,-329]}],\"laneId\":6,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":5,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.090959,39.5878557],[-105.0909501,39.5878218]]},\"properties\":{\"nodes\":[{\"delta\":[-630,-2062],\"delevation\":10},{\"delta\":[76,-377],\"delevation\":10}],\"laneId\":5,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":4,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.090914,39.5878612],[-105.0909051,39.5878298]]},\"properties\":{\"nodes\":[{\"delta\":[-245,-2001],\"delevation\":10},{\"delta\":[76,-349]}],\"laneId\":4,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":10,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911626,39.5880622],[-105.0912043,39.5880536]]},\"properties\":{\"nodes\":[{\"delta\":[-2374,232],\"delevation\":10},{\"delta\":[-357,-96]}],\"laneId\":10,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":4,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":8,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911477,39.587995],[-105.0914565,39.5879427],[-105.0917937,39.5879029],[-105.0922121,39.5878724],[-105.0926509,39.5878748],[-105.0930303,39.5879073],[-105.0932697,39.5879503],[-105.0937243,39.5880569],[-105.0940309,39.5881258],[-105.0943257,39.5881804],[-105.094592,39.5882097]]},\"properties\":{\"nodes\":[{\"delta\":[-2246,-514],\"delevation\":10},{\"delta\":[-2644,-581]},{\"delta\":[-2887,-442],\"delevation\":10},{\"delta\":[-3583,-339],\"delevation\":10},{\"delta\":[-3757,27]},{\"delta\":[-3249,361],\"delevation\":-10},{\"delta\":[-2050,478]},{\"delta\":[-3893,1184]},{\"delta\":[-2625,766],\"delevation\":-10},{\"delta\":[-2524,607],\"delevation\":10},{\"delta\":[-2280,325],\"delevation\":10}],\"laneId\":8,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":7,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911442,39.5879589],[-105.0914154,39.5879165],[-105.0916346,39.5878851],[-105.0918433,39.5878639],[-105.0921546,39.5878547]]},\"properties\":{\"nodes\":[{\"delta\":[-2216,-915],\"delevation\":10},{\"delta\":[-2322,-471]},{\"delta\":[-1877,-349],\"delevation\":10},{\"delta\":[-1787,-235]},{\"delta\":[-2666,-102],\"delevation\":10}],\"laneId\":7,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":6,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":9,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911534,39.5880261],[-105.091436,39.5879812],[-105.0916658,39.5879507],[-105.091881,39.5879277],[-105.0921287,39.5878972]]},\"properties\":{\"nodes\":[{\"delta\":[-2295,-169],\"delevation\":10},{\"delta\":[-2420,-499]},{\"delta\":[-1968,-339],\"delevation\":10},{\"delta\":[-1843,-256]},{\"delta\":[-2121,-339]}],\"laneId\":9,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":12,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910447,39.5881948],[-105.0911481,39.5886317],[-105.091196,39.588862],[-105.0912349,39.5890282],[-105.0912722,39.5893202],[-105.0913306,39.5897261],[-105.0913695,39.5900324],[-105.0914008,39.5903008],[-105.0914893,39.5913099],[-105.091527,39.5923157]]},\"properties\":{\"nodes\":[{\"delta\":[-1364,1705],\"delevation\":10},{\"delta\":[-885,4854],\"delevation\":-30},{\"delta\":[-410,2559],\"delevation\":10},{\"delta\":[-333,1847],\"delevation\":-10},{\"delta\":[-319,3244],\"delevation\":-20},{\"delta\":[-500,4510]},{\"delta\":[-333,3403],\"delevation\":-30},{\"delta\":[-268,2982]},{\"delta\":[-758,11212],\"delevation\":-30},{\"delta\":[-323,11176],\"delevation\":-70}],\"laneId\":12,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":5,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":13,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910013,39.5881975],[-105.0911059,39.5886309],[-105.091144,39.5888313],[-105.0911829,39.5890442],[-105.0912308,39.5893169],[-105.0912689,39.5895877],[-105.0913005,39.5898143],[-105.0913313,39.5900714],[-105.0913597,39.5902968],[-105.0914461,39.5913017],[-105.0914756,39.592324]]},\"properties\":{\"nodes\":[{\"delta\":[-992,1735],\"delevation\":10},{\"delta\":[-896,4816],\"delevation\":-30},{\"delta\":[-326,2227],\"delevation\":10},{\"delta\":[-333,2366]},{\"delta\":[-410,3030],\"delevation\":-20},{\"delta\":[-326,3009],\"delevation\":-10},{\"delta\":[-271,2518],\"delevation\":-10},{\"delta\":[-264,2857],\"delevation\":-20},{\"delta\":[-243,2504]},{\"delta\":[-740,11165],\"delevation\":-30},{\"delta\":[-253,11359],\"delevation\":-70}],\"laneId\":13,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":4,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":11,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910891,39.5881859],[-105.0911549,39.5884681],[-105.091196,39.5886783],[-105.091222,39.5888049],[-105.0912401,39.5889649]]},\"properties\":{\"nodes\":[{\"delta\":[-1744,1607],\"delevation\":10},{\"delta\":[-563,3136],\"delevation\":-20},{\"delta\":[-352,2336],\"delevation\":-10},{\"delta\":[-223,1407],\"delevation\":10},{\"delta\":[-155,1778]}],\"laneId\":11,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":14,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0908389,39.5882151],[-105.0908478,39.5882471]]},\"properties\":{\"nodes\":[{\"delta\":[398,1931],\"delevation\":-10},{\"delta\":[-76,356]}],\"laneId\":14,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":15,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907875,39.58822],[-105.0907979,39.5882514]]},\"properties\":{\"nodes\":[{\"delta\":[838,1985],\"delevation\":-20},{\"delta\":[-89,349]}],\"laneId\":15,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}}]},\"connectingLanesFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":\"1-15\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907089,39.587905],[-105.0907875,39.58822]]},\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":1,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"2-14\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907462,39.5878956],[-105.0908389,39.5882151]]},\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":2,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"3-10\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907914,39.5878879],[-105.0911626,39.5880622]]},\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":3,\"egressLaneId\":10}},{\"type\":\"Feature\",\"id\":\"8-15\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911477,39.587995],[-105.0907875,39.58822]]},\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":8,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"7-6\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911442,39.5879589],[-105.0910008,39.5878477]]},\"properties\":{\"ingressLaneId\":7,\"egressLaneId\":6}},{\"type\":\"Feature\",\"id\":\"9-14\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911534,39.5880261],[-105.0908389,39.5882151]]},\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":9,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"12-5\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910447,39.5881948],[-105.090959,39.5878557]]},\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":12,\"egressLaneId\":5}},{\"type\":\"Feature\",\"id\":\"13-4\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910013,39.5881975],[-105.090914,39.5878612]]},\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":13,\"egressLaneId\":4}},{\"type\":\"Feature\",\"id\":\"11-10\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910891,39.5881859],[-105.0911626,39.5880622]]},\"properties\":{\"ingressLaneId\":11,\"egressLaneId\":10}}]},\"properties\":{\"messageType\":\"MAP\",\"odeReceivedAt\":\"2024-02-22T23:26:25.107Z\",\"originIp\":\"10.11.81.12\",\"intersectionId\":12109,\"msgIssueRevision\":2,\"revision\":2,\"refPoint\":{\"latitude\":39.5880413,\"longitude\":-105.0908854,\"elevation\":1500},\"cti4501Conformant\":false,\"validationMessages\":[{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].id.region: null found, integer expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].id.region\",\"schemaPath\":\"#/$defs/J2735RoadRegulatorID/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[8].connectsTo.connectsTo[0].signalGroup: null found, integer expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[8].connectsTo.connectsTo[0].signalGroup\",\"schemaPath\":\"#/$defs/J2735SignalGroupID/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[12].connectsTo.connectsTo[0].signalGroup: null found, integer expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[12].connectsTo.connectsTo[0].signalGroup\",\"schemaPath\":\"#/$defs/J2735SignalGroupID/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].speedLimits: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].speedLimits\",\"schemaPath\":\"#/$defs/J2735SpeedLimitList_Wrapper/type\"}],\"laneWidth\":366,\"mapSource\":\"RSU\",\"timeStamp\":\"2024-02-22T23:26:25.107Z\"}}"; + String inputProcessedMap4 = "{\"mapFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":1,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907089,39.587905],[-105.0906245,39.5876246],[-105.0905203,39.587281],[-105.0904383,39.5870554],[-105.0903588,39.5868383],[-105.0902622,39.5865865],[-105.0901249,39.5862612],[-105.0900451,39.5860819],[-105.0899283,39.5858283],[-105.0898739,39.5857117],[-105.0895814,39.5851569],[-105.0888764,39.5839527]]},\"properties\":{\"nodes\":[{\"delta\":[1511,-1514]},{\"delta\":[723,-3116],\"delevation\":10},{\"delta\":[892,-3818],\"delevation\":20},{\"delta\":[702,-2507],\"delevation\":20},{\"delta\":[681,-2412],\"delevation\":10},{\"delta\":[827,-2798],\"delevation\":10},{\"delta\":[1176,-3614],\"delevation\":20},{\"delta\":[683,-1992]},{\"delta\":[1000,-2818],\"delevation\":10},{\"delta\":[466,-1295],\"delevation\":20},{\"delta\":[2505,-6164],\"delevation\":20},{\"delta\":[6037,-13380],\"delevation\":70}],\"laneId\":1,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":2,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907462,39.5878956],[-105.090652,39.5875596],[-105.090534,39.5871793],[-105.0903457,39.5866864],[-105.0902123,39.5863581],[-105.0900802,39.5860572],[-105.0898164,39.5855019],[-105.0895409,39.5849856],[-105.088922,39.5839259]]},\"properties\":{\"nodes\":[{\"delta\":[1192,-1619]},{\"delta\":[807,-3733],\"delevation\":30},{\"delta\":[1010,-4226],\"delevation\":10},{\"delta\":[1612,-5477],\"delevation\":30},{\"delta\":[1142,-3648],\"delevation\":20},{\"delta\":[1131,-3343],\"delevation\":10},{\"delta\":[2259,-6170],\"delevation\":30},{\"delta\":[2359,-5737],\"delevation\":30},{\"delta\":[5300,-11774],\"delevation\":50}],\"laneId\":2,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":3,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907914,39.5878879],[-105.090747,39.5877247],[-105.0906498,39.5874141],[-105.0906262,39.5873356],[-105.0905865,39.5872922]]},\"properties\":{\"nodes\":[{\"delta\":[805,-1704],\"delevation\":10},{\"delta\":[380,-1813]},{\"delta\":[832,-3451],\"delevation\":30},{\"delta\":[202,-872]},{\"delta\":[340,-482],\"delevation\":-10}],\"laneId\":3,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":6,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910008,39.5878477],[-105.0909927,39.5878181]]},\"properties\":{\"nodes\":[{\"delta\":[-988,-2151],\"delevation\":20},{\"delta\":[69,-329]}],\"laneId\":6,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":5,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.090959,39.5878557],[-105.0909501,39.5878218]]},\"properties\":{\"nodes\":[{\"delta\":[-630,-2062],\"delevation\":10},{\"delta\":[76,-377],\"delevation\":10}],\"laneId\":5,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":4,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.090914,39.5878612],[-105.0909051,39.5878298]]},\"properties\":{\"nodes\":[{\"delta\":[-245,-2001],\"delevation\":10},{\"delta\":[76,-349]}],\"laneId\":4,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":10,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911626,39.5880622],[-105.0912043,39.5880536]]},\"properties\":{\"nodes\":[{\"delta\":[-2374,232],\"delevation\":10},{\"delta\":[-357,-96]}],\"laneId\":10,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":4,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":8,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911477,39.587995],[-105.0914565,39.5879427],[-105.0917937,39.5879029],[-105.0922121,39.5878724],[-105.0926509,39.5878748],[-105.0930303,39.5879073],[-105.0932697,39.5879503],[-105.0937243,39.5880569],[-105.0940309,39.5881258],[-105.0943257,39.5881804],[-105.094592,39.5882097]]},\"properties\":{\"nodes\":[{\"delta\":[-2246,-514],\"delevation\":10},{\"delta\":[-2644,-581]},{\"delta\":[-2887,-442],\"delevation\":10},{\"delta\":[-3583,-339],\"delevation\":10},{\"delta\":[-3757,27]},{\"delta\":[-3249,361],\"delevation\":-10},{\"delta\":[-2050,478]},{\"delta\":[-3893,1184]},{\"delta\":[-2625,766],\"delevation\":-10},{\"delta\":[-2524,607],\"delevation\":10},{\"delta\":[-2280,325],\"delevation\":10}],\"laneId\":8,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":7,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911442,39.5879589],[-105.0914154,39.5879165],[-105.0916346,39.5878851],[-105.0918433,39.5878639],[-105.0921546,39.5878547]]},\"properties\":{\"nodes\":[{\"delta\":[-2216,-915],\"delevation\":10},{\"delta\":[-2322,-471]},{\"delta\":[-1877,-349],\"delevation\":10},{\"delta\":[-1787,-235]},{\"delta\":[-2666,-102],\"delevation\":10}],\"laneId\":7,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":6,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":9,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911534,39.5880261],[-105.091436,39.5879812],[-105.0916658,39.5879507],[-105.091881,39.5879277],[-105.0921287,39.5878972]]},\"properties\":{\"nodes\":[{\"delta\":[-2295,-169],\"delevation\":10},{\"delta\":[-2420,-499]},{\"delta\":[-1968,-339],\"delevation\":10},{\"delta\":[-1843,-256]},{\"delta\":[-2121,-339]}],\"laneId\":9,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":12,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910447,39.5881948],[-105.0911481,39.5886317],[-105.091196,39.588862],[-105.0912349,39.5890282],[-105.0912722,39.5893202],[-105.0913306,39.5897261],[-105.0913695,39.5900324],[-105.0914008,39.5903008],[-105.0914893,39.5913099],[-105.091527,39.5923157]]},\"properties\":{\"nodes\":[{\"delta\":[-1364,1705],\"delevation\":10},{\"delta\":[-885,4854],\"delevation\":-30},{\"delta\":[-410,2559],\"delevation\":10},{\"delta\":[-333,1847],\"delevation\":-10},{\"delta\":[-319,3244],\"delevation\":-20},{\"delta\":[-500,4510]},{\"delta\":[-333,3403],\"delevation\":-30},{\"delta\":[-268,2982]},{\"delta\":[-758,11212],\"delevation\":-30},{\"delta\":[-323,11176],\"delevation\":-70}],\"laneId\":12,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":5,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":13,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910013,39.5881975],[-105.0911059,39.5886309],[-105.091144,39.5888313],[-105.0911829,39.5890442],[-105.0912308,39.5893169],[-105.0912689,39.5895877],[-105.0913005,39.5898143],[-105.0913313,39.5900714],[-105.0913597,39.5902968],[-105.0914461,39.5913017],[-105.0914756,39.592324]]},\"properties\":{\"nodes\":[{\"delta\":[-992,1735],\"delevation\":10},{\"delta\":[-896,4816],\"delevation\":-30},{\"delta\":[-326,2227],\"delevation\":10},{\"delta\":[-333,2366]},{\"delta\":[-410,3030],\"delevation\":-20},{\"delta\":[-326,3009],\"delevation\":-10},{\"delta\":[-271,2518],\"delevation\":-10},{\"delta\":[-264,2857],\"delevation\":-20},{\"delta\":[-243,2504]},{\"delta\":[-740,11165],\"delevation\":-30},{\"delta\":[-253,11359],\"delevation\":-70}],\"laneId\":13,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":4,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":11,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910891,39.5881859],[-105.0911549,39.5884681],[-105.091196,39.5886783],[-105.091222,39.5888049],[-105.0912401,39.5889649]]},\"properties\":{\"nodes\":[{\"delta\":[-1744,1607],\"delevation\":10},{\"delta\":[-563,3136],\"delevation\":-20},{\"delta\":[-352,2336],\"delevation\":-10},{\"delta\":[-223,1407],\"delevation\":10},{\"delta\":[-155,1778]}],\"laneId\":11,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":14,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0908389,39.5882151],[-105.0908478,39.5882471]]},\"properties\":{\"nodes\":[{\"delta\":[398,1931],\"delevation\":-10},{\"delta\":[-76,356]}],\"laneId\":14,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":15,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907875,39.58822],[-105.0907979,39.5882514]]},\"properties\":{\"nodes\":[{\"delta\":[838,1985],\"delevation\":-20},{\"delta\":[-89,349]}],\"laneId\":15,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}}]},\"connectingLanesFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":\"1-15\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907089,39.587905],[-105.0907875,39.58822]]},\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":1,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"2-14\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907462,39.5878956],[-105.0908389,39.5882151]]},\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":2,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"3-10\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907914,39.5878879],[-105.0911626,39.5880622]]},\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":3,\"egressLaneId\":10}},{\"type\":\"Feature\",\"id\":\"8-15\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911477,39.587995],[-105.0907875,39.58822]]},\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":8,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"7-6\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911442,39.5879589],[-105.0910008,39.5878477]]},\"properties\":{\"ingressLaneId\":7,\"egressLaneId\":6}},{\"type\":\"Feature\",\"id\":\"9-14\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911534,39.5880261],[-105.0908389,39.5882151]]},\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":9,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"12-5\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910447,39.5881948],[-105.090959,39.5878557]]},\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":12,\"egressLaneId\":5}},{\"type\":\"Feature\",\"id\":\"13-4\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910013,39.5881975],[-105.090914,39.5878612]]},\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":13,\"egressLaneId\":4}},{\"type\":\"Feature\",\"id\":\"11-10\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910891,39.5881859],[-105.0911626,39.5880622]]},\"properties\":{\"ingressLaneId\":11,\"egressLaneId\":10}}]},\"properties\":{\"messageType\":\"MAP\",\"odeReceivedAt\":\"2024-02-22T23:26:26.114Z\",\"originIp\":\"10.11.81.12\",\"intersectionId\":12109,\"msgIssueRevision\":2,\"revision\":2,\"refPoint\":{\"latitude\":39.5880413,\"longitude\":-105.0908854,\"elevation\":1691},\"cti4501Conformant\":false,\"validationMessages\":[{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].id.region: null found, integer expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].id.region\",\"schemaPath\":\"#/$defs/J2735RoadRegulatorID/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[8].connectsTo.connectsTo[0].signalGroup: null found, integer expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[8].connectsTo.connectsTo[0].signalGroup\",\"schemaPath\":\"#/$defs/J2735SignalGroupID/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[12].connectsTo.connectsTo[0].signalGroup: null found, integer expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[12].connectsTo.connectsTo[0].signalGroup\",\"schemaPath\":\"#/$defs/J2735SignalGroupID/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].speedLimits: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].speedLimits\",\"schemaPath\":\"#/$defs/J2735SpeedLimitList_Wrapper/type\"}],\"laneWidth\":366,\"mapSource\":\"RSU\",\"timeStamp\":\"2024-02-22T23:26:26.114Z\"}}"; + + + String key = "{\"rsuId\":\"10.11.81.12\",\"intersectionId\":12109,\"region\":-1}"; + + @Autowired + DeduplicatorProperties props; + + + @Test + public void testTopology() { + + props = new DeduplicatorProperties(); + props.setKafkaTopicProcessedMap(inputTopic); + props.setKafkaTopicDeduplicatedProcessedMap(outputTopic); + + ProcessedMapDeduplicatorTopology processedMapDeduplicatorTopology = new ProcessedMapDeduplicatorTopology(props, null); + + Topology topology = processedMapDeduplicatorTopology.buildTopology(); + objectMapper.registerModule(new JavaTimeModule()); + + try (TopologyTestDriver driver = new TopologyTestDriver(topology)) { + + + TestInputTopic inputProcessedMapData = driver.createInputTopic( + inputTopic, + Serdes.String().serializer(), + Serdes.String().serializer()); + + + TestOutputTopic> outputProcessedMapData = driver.createOutputTopic( + outputTopic, + Serdes.String().deserializer(), + JsonSerdes.ProcessedMapGeoJson().deserializer()); + + inputProcessedMapData.pipeInput(key, inputProcessedMap1); + inputProcessedMapData.pipeInput(key, inputProcessedMap2); + inputProcessedMapData.pipeInput(key, inputProcessedMap3); + inputProcessedMapData.pipeInput(key, inputProcessedMap4); + + List>> mapDeduplicatorResults = outputProcessedMapData.readKeyValuesToList(); + + // validate that only 3 messages make it through + assertEquals(3, mapDeduplicatorResults.size()); + + ProcessedMap map1 = objectMapper.readValue(inputProcessedMap1, typeReference); + ProcessedMap map3 = objectMapper.readValue(inputProcessedMap3, typeReference); + ProcessedMap map4 = objectMapper.readValue(inputProcessedMap4, typeReference); + + assertEquals(map1.getProperties().getOdeReceivedAt(), mapDeduplicatorResults.get(0).value.getProperties().getOdeReceivedAt()); + assertEquals(map3.getProperties().getOdeReceivedAt(), mapDeduplicatorResults.get(1).value.getProperties().getOdeReceivedAt()); + assertEquals(map4.getProperties().getOdeReceivedAt(), mapDeduplicatorResults.get(2).value.getProperties().getOdeReceivedAt()); + + } catch (JsonMappingException e) { + e.printStackTrace(); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/ProcessedMapWktDeduplicatorTopologyTest.java b/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/ProcessedMapWktDeduplicatorTopologyTest.java new file mode 100644 index 0000000..7b1d2fb --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/ProcessedMapWktDeduplicatorTopologyTest.java @@ -0,0 +1,101 @@ +package deduplicator; + +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.TestInputTopic; +import org.apache.kafka.streams.TestOutputTopic; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.TopologyTestDriver; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.topologies.ProcessedMapWktDeduplicatorTopology; +import us.dot.its.jpo.geojsonconverter.pojos.geojson.map.ProcessedMap; +import us.dot.its.jpo.geojsonconverter.serialization.JsonSerdes; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +public class ProcessedMapWktDeduplicatorTopologyTest { + + String inputTopic = "topic.ProcessedMapWKT"; + String outputTopic = "topic.DeduplicatedProcessedMapWKT"; + + + TypeReference> typeReference = new TypeReference<>(){}; + ObjectMapper objectMapper = new ObjectMapper(); + + String inputProcessedMapWkt1 = "{\"mapFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":1,\"geometry\":\"LINESTRING (-105.0907089 39.587905, -105.0906245 39.5876246, -105.0905203 39.587281, -105.0904383 39.5870554, -105.0903588 39.5868383, -105.0902622 39.5865865, -105.0901249 39.5862612, -105.0900451 39.5860819, -105.0899283 39.5858283, -105.0898739 39.5857117, -105.0895814 39.5851569, -105.0888764 39.5839527)\",\"properties\":{\"laneId\":1,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":2,\"geometry\":\"LINESTRING (-105.0907462 39.5878956, -105.090652 39.5875596, -105.090534 39.5871793, -105.0903457 39.5866864, -105.0902123 39.5863581, -105.0900802 39.5860572, -105.0898164 39.5855019, -105.0895409 39.5849856, -105.088922 39.5839259)\",\"properties\":{\"laneId\":2,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":3,\"geometry\":\"LINESTRING (-105.0907914 39.5878879, -105.090747 39.5877247, -105.0906498 39.5874141, -105.0906262 39.5873356, -105.0905865 39.5872922)\",\"properties\":{\"laneId\":3,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":6,\"geometry\":\"LINESTRING (-105.0910008 39.5878477, -105.0909927 39.5878181)\",\"properties\":{\"laneId\":6,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":5,\"geometry\":\"LINESTRING (-105.090959 39.5878557, -105.0909501 39.5878218)\",\"properties\":{\"laneId\":5,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":4,\"geometry\":\"LINESTRING (-105.090914 39.5878612, -105.0909051 39.5878298)\",\"properties\":{\"laneId\":4,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":10,\"geometry\":\"LINESTRING (-105.0911626 39.5880622, -105.0912043 39.5880536)\",\"properties\":{\"laneId\":10,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":4,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":8,\"geometry\":\"LINESTRING (-105.0911477 39.587995, -105.0914565 39.5879427, -105.0917937 39.5879029, -105.0922121 39.5878724, -105.0926509 39.5878748, -105.0930303 39.5879073, -105.0932697 39.5879503, -105.0937243 39.5880569, -105.0940309 39.5881258, -105.0943257 39.5881804, -105.094592 39.5882097)\",\"properties\":{\"laneId\":8,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":7,\"geometry\":\"LINESTRING (-105.0911442 39.5879589, -105.0914154 39.5879165, -105.0916346 39.5878851, -105.0918433 39.5878639, -105.0921546 39.5878547)\",\"properties\":{\"laneId\":7,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":6,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":9,\"geometry\":\"LINESTRING (-105.0911534 39.5880261, -105.091436 39.5879812, -105.0916658 39.5879507, -105.091881 39.5879277, -105.0921287 39.5878972)\",\"properties\":{\"laneId\":9,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":12,\"geometry\":\"LINESTRING (-105.0910447 39.5881948, -105.0911481 39.5886317, -105.091196 39.588862, -105.0912349 39.5890282, -105.0912722 39.5893202, -105.0913306 39.5897261, -105.0913695 39.5900324, -105.0914008 39.5903008, -105.0914893 39.5913099, -105.091527 39.5923157)\",\"properties\":{\"laneId\":12,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":5,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":13,\"geometry\":\"LINESTRING (-105.0910013 39.5881975, -105.0911059 39.5886309, -105.091144 39.5888313, -105.0911829 39.5890442, -105.0912308 39.5893169, -105.0912689 39.5895877, -105.0913005 39.5898143, -105.0913313 39.5900714, -105.0913597 39.5902968, -105.0914461 39.5913017, -105.0914756 39.592324)\",\"properties\":{\"laneId\":13,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":4,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":11,\"geometry\":\"LINESTRING (-105.0910891 39.5881859, -105.0911549 39.5884681, -105.091196 39.5886783, -105.091222 39.5888049, -105.0912401 39.5889649)\",\"properties\":{\"laneId\":11,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":14,\"geometry\":\"LINESTRING (-105.0908389 39.5882151, -105.0908478 39.5882471)\",\"properties\":{\"laneId\":14,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":15,\"geometry\":\"LINESTRING (-105.0907875 39.58822, -105.0907979 39.5882514)\",\"properties\":{\"laneId\":15,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}}]},\"connectingLanesFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":\"1-15\",\"geometry\":\"LINESTRING (-105.0907089 39.587905, -105.0907875 39.58822)\",\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":1,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"2-14\",\"geometry\":\"LINESTRING (-105.0907462 39.5878956, -105.0908389 39.5882151)\",\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":2,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"3-10\",\"geometry\":\"LINESTRING (-105.0907914 39.5878879, -105.0911626 39.5880622)\",\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":3,\"egressLaneId\":10}},{\"type\":\"Feature\",\"id\":\"8-15\",\"geometry\":\"LINESTRING (-105.0911477 39.587995, -105.0907875 39.58822)\",\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":8,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"7-6\",\"geometry\":\"LINESTRING (-105.0911442 39.5879589, -105.0910008 39.5878477)\",\"properties\":{\"ingressLaneId\":7,\"egressLaneId\":6}},{\"type\":\"Feature\",\"id\":\"9-14\",\"geometry\":\"LINESTRING (-105.0911534 39.5880261, -105.0908389 39.5882151)\",\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":9,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"12-5\",\"geometry\":\"LINESTRING (-105.0910447 39.5881948, -105.090959 39.5878557)\",\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":12,\"egressLaneId\":5}},{\"type\":\"Feature\",\"id\":\"13-4\",\"geometry\":\"LINESTRING (-105.0910013 39.5881975, -105.090914 39.5878612)\",\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":13,\"egressLaneId\":4}},{\"type\":\"Feature\",\"id\":\"11-10\",\"geometry\":\"LINESTRING (-105.0910891 39.5881859, -105.0911626 39.5880622)\",\"properties\":{\"ingressLaneId\":11,\"egressLaneId\":10}}]},\"properties\":{\"messageType\":\"MAP\",\"odeReceivedAt\":\"2024-05-06T21:35:21.713Z\",\"originIp\":\"10.11.81.12\",\"intersectionId\":12109,\"msgIssueRevision\":2,\"revision\":2,\"refPoint\":{\"latitude\":39.5880413,\"longitude\":-105.0908854,\"elevation\":1500},\"cti4501Conformant\":false,\"laneWidth\":366,\"mapSource\":\"RSU\",\"timeStamp\":\"2024-05-06T21:35:21.713Z\"}}"; + + + //String inputProcessedMapWkt1 = "{\"mapFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":1,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907089,39.587905],[-105.0906245,39.5876246],[-105.0905203,39.587281],[-105.0904383,39.5870554],[-105.0903588,39.5868383],[-105.0902622,39.5865865],[-105.0901249,39.5862612],[-105.0900451,39.5860819],[-105.0899283,39.5858283],[-105.0898739,39.5857117],[-105.0895814,39.5851569],[-105.0888764,39.5839527]]},\"properties\":{\"nodes\":[{\"delta\":[1511,-1514]},{\"delta\":[723,-3116],\"delevation\":10},{\"delta\":[892,-3818],\"delevation\":20},{\"delta\":[702,-2507],\"delevation\":20},{\"delta\":[681,-2412],\"delevation\":10},{\"delta\":[827,-2798],\"delevation\":10},{\"delta\":[1176,-3614],\"delevation\":20},{\"delta\":[683,-1992]},{\"delta\":[1000,-2818],\"delevation\":10},{\"delta\":[466,-1295],\"delevation\":20},{\"delta\":[2505,-6164],\"delevation\":20},{\"delta\":[6037,-13380],\"delevation\":70}],\"laneId\":1,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":2,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907462,39.5878956],[-105.090652,39.5875596],[-105.090534,39.5871793],[-105.0903457,39.5866864],[-105.0902123,39.5863581],[-105.0900802,39.5860572],[-105.0898164,39.5855019],[-105.0895409,39.5849856],[-105.088922,39.5839259]]},\"properties\":{\"nodes\":[{\"delta\":[1192,-1619]},{\"delta\":[807,-3733],\"delevation\":30},{\"delta\":[1010,-4226],\"delevation\":10},{\"delta\":[1612,-5477],\"delevation\":30},{\"delta\":[1142,-3648],\"delevation\":20},{\"delta\":[1131,-3343],\"delevation\":10},{\"delta\":[2259,-6170],\"delevation\":30},{\"delta\":[2359,-5737],\"delevation\":30},{\"delta\":[5300,-11774],\"delevation\":50}],\"laneId\":2,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":3,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907914,39.5878879],[-105.090747,39.5877247],[-105.0906498,39.5874141],[-105.0906262,39.5873356],[-105.0905865,39.5872922]]},\"properties\":{\"nodes\":[{\"delta\":[805,-1704],\"delevation\":10},{\"delta\":[380,-1813]},{\"delta\":[832,-3451],\"delevation\":30},{\"delta\":[202,-872]},{\"delta\":[340,-482],\"delevation\":-10}],\"laneId\":3,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":6,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910008,39.5878477],[-105.0909927,39.5878181]]},\"properties\":{\"nodes\":[{\"delta\":[-988,-2151],\"delevation\":20},{\"delta\":[69,-329]}],\"laneId\":6,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":5,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.090959,39.5878557],[-105.0909501,39.5878218]]},\"properties\":{\"nodes\":[{\"delta\":[-630,-2062],\"delevation\":10},{\"delta\":[76,-377],\"delevation\":10}],\"laneId\":5,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":4,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.090914,39.5878612],[-105.0909051,39.5878298]]},\"properties\":{\"nodes\":[{\"delta\":[-245,-2001],\"delevation\":10},{\"delta\":[76,-349]}],\"laneId\":4,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":10,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911626,39.5880622],[-105.0912043,39.5880536]]},\"properties\":{\"nodes\":[{\"delta\":[-2374,232],\"delevation\":10},{\"delta\":[-357,-96]}],\"laneId\":10,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":4,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":8,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911477,39.587995],[-105.0914565,39.5879427],[-105.0917937,39.5879029],[-105.0922121,39.5878724],[-105.0926509,39.5878748],[-105.0930303,39.5879073],[-105.0932697,39.5879503],[-105.0937243,39.5880569],[-105.0940309,39.5881258],[-105.0943257,39.5881804],[-105.094592,39.5882097]]},\"properties\":{\"nodes\":[{\"delta\":[-2246,-514],\"delevation\":10},{\"delta\":[-2644,-581]},{\"delta\":[-2887,-442],\"delevation\":10},{\"delta\":[-3583,-339],\"delevation\":10},{\"delta\":[-3757,27]},{\"delta\":[-3249,361],\"delevation\":-10},{\"delta\":[-2050,478]},{\"delta\":[-3893,1184]},{\"delta\":[-2625,766],\"delevation\":-10},{\"delta\":[-2524,607],\"delevation\":10},{\"delta\":[-2280,325],\"delevation\":10}],\"laneId\":8,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":7,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911442,39.5879589],[-105.0914154,39.5879165],[-105.0916346,39.5878851],[-105.0918433,39.5878639],[-105.0921546,39.5878547]]},\"properties\":{\"nodes\":[{\"delta\":[-2216,-915],\"delevation\":10},{\"delta\":[-2322,-471]},{\"delta\":[-1877,-349],\"delevation\":10},{\"delta\":[-1787,-235]},{\"delta\":[-2666,-102],\"delevation\":10}],\"laneId\":7,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":6,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":9,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911534,39.5880261],[-105.091436,39.5879812],[-105.0916658,39.5879507],[-105.091881,39.5879277],[-105.0921287,39.5878972]]},\"properties\":{\"nodes\":[{\"delta\":[-2295,-169],\"delevation\":10},{\"delta\":[-2420,-499]},{\"delta\":[-1968,-339],\"delevation\":10},{\"delta\":[-1843,-256]},{\"delta\":[-2121,-339]}],\"laneId\":9,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":12,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910447,39.5881948],[-105.0911481,39.5886317],[-105.091196,39.588862],[-105.0912349,39.5890282],[-105.0912722,39.5893202],[-105.0913306,39.5897261],[-105.0913695,39.5900324],[-105.0914008,39.5903008],[-105.0914893,39.5913099],[-105.091527,39.5923157]]},\"properties\":{\"nodes\":[{\"delta\":[-1364,1705],\"delevation\":10},{\"delta\":[-885,4854],\"delevation\":-30},{\"delta\":[-410,2559],\"delevation\":10},{\"delta\":[-333,1847],\"delevation\":-10},{\"delta\":[-319,3244],\"delevation\":-20},{\"delta\":[-500,4510]},{\"delta\":[-333,3403],\"delevation\":-30},{\"delta\":[-268,2982]},{\"delta\":[-758,11212],\"delevation\":-30},{\"delta\":[-323,11176],\"delevation\":-70}],\"laneId\":12,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":5,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":13,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910013,39.5881975],[-105.0911059,39.5886309],[-105.091144,39.5888313],[-105.0911829,39.5890442],[-105.0912308,39.5893169],[-105.0912689,39.5895877],[-105.0913005,39.5898143],[-105.0913313,39.5900714],[-105.0913597,39.5902968],[-105.0914461,39.5913017],[-105.0914756,39.592324]]},\"properties\":{\"nodes\":[{\"delta\":[-992,1735],\"delevation\":10},{\"delta\":[-896,4816],\"delevation\":-30},{\"delta\":[-326,2227],\"delevation\":10},{\"delta\":[-333,2366]},{\"delta\":[-410,3030],\"delevation\":-20},{\"delta\":[-326,3009],\"delevation\":-10},{\"delta\":[-271,2518],\"delevation\":-10},{\"delta\":[-264,2857],\"delevation\":-20},{\"delta\":[-243,2504]},{\"delta\":[-740,11165],\"delevation\":-30},{\"delta\":[-253,11359],\"delevation\":-70}],\"laneId\":13,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":4,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":11,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910891,39.5881859],[-105.0911549,39.5884681],[-105.091196,39.5886783],[-105.091222,39.5888049],[-105.0912401,39.5889649]]},\"properties\":{\"nodes\":[{\"delta\":[-1744,1607],\"delevation\":10},{\"delta\":[-563,3136],\"delevation\":-20},{\"delta\":[-352,2336],\"delevation\":-10},{\"delta\":[-223,1407],\"delevation\":10},{\"delta\":[-155,1778]}],\"laneId\":11,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":14,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0908389,39.5882151],[-105.0908478,39.5882471]]},\"properties\":{\"nodes\":[{\"delta\":[398,1931],\"delevation\":-10},{\"delta\":[-76,356]}],\"laneId\":14,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":15,\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907875,39.58822],[-105.0907979,39.5882514]]},\"properties\":{\"nodes\":[{\"delta\":[838,1985],\"delevation\":-20},{\"delta\":[-89,349]}],\"laneId\":15,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}}]},\"connectingLanesFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":\"1-15\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907089,39.587905],[-105.0907875,39.58822]]},\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":1,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"2-14\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907462,39.5878956],[-105.0908389,39.5882151]]},\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":2,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"3-10\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0907914,39.5878879],[-105.0911626,39.5880622]]},\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":3,\"egressLaneId\":10}},{\"type\":\"Feature\",\"id\":\"8-15\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911477,39.587995],[-105.0907875,39.58822]]},\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":8,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"7-6\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911442,39.5879589],[-105.0910008,39.5878477]]},\"properties\":{\"ingressLaneId\":7,\"egressLaneId\":6}},{\"type\":\"Feature\",\"id\":\"9-14\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0911534,39.5880261],[-105.0908389,39.5882151]]},\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":9,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"12-5\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910447,39.5881948],[-105.090959,39.5878557]]},\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":12,\"egressLaneId\":5}},{\"type\":\"Feature\",\"id\":\"13-4\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910013,39.5881975],[-105.090914,39.5878612]]},\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":13,\"egressLaneId\":4}},{\"type\":\"Feature\",\"id\":\"11-10\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-105.0910891,39.5881859],[-105.0911626,39.5880622]]},\"properties\":{\"ingressLaneId\":11,\"egressLaneId\":10}}]},\"properties\":{\"messageType\":\"MAP\",\"odeReceivedAt\":\"2024-02-22T23:26:26.114Z\",\"originIp\":\"10.11.81.12\",\"intersectionId\":12109,\"msgIssueRevision\":2,\"revision\":2,\"refPoint\":{\"latitude\":39.5880413,\"longitude\":-105.0908854,\"elevation\":1691},\"cti4501Conformant\":false,\"validationMessages\":[{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].id.region: null found, integer expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].id.region\",\"schemaPath\":\"#/$defs/J2735RoadRegulatorID/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[3].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[4].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[5].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[6].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[8].connectsTo.connectsTo[0].signalGroup: null found, integer expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[8].connectsTo.connectsTo[0].signalGroup\",\"schemaPath\":\"#/$defs/J2735SignalGroupID/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[12].connectsTo.connectsTo[0].signalGroup: null found, integer expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[12].connectsTo.connectsTo[0].signalGroup\",\"schemaPath\":\"#/$defs/J2735SignalGroupID/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[13].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].connectsTo: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].connectsTo\",\"schemaPath\":\"#/$defs/J2735ConnectsToList_Wrapper/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].maneuvers: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].laneSet.GenericLane[14].maneuvers\",\"schemaPath\":\"#/$defs/J2735AllowedManeuvers/type\"},{\"message\":\"$.payload.data.intersections.intersectionGeometry[0].speedLimits: null found, object expected\",\"jsonPath\":\"$.payload.data.intersections.intersectionGeometry[0].speedLimits\",\"schemaPath\":\"#/$defs/J2735SpeedLimitList_Wrapper/type\"}],\"laneWidth\":366,\"mapSource\":\"RSU\",\"timeStamp\":\"2024-02-22T23:26:26.114Z\"}}"; + String inputProcessedMapWkt2 = "{\"mapFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":1,\"geometry\":\"LINESTRING (-105.0907089 39.587905, -105.0906245 39.5876246, -105.0905203 39.587281, -105.0904383 39.5870554, -105.0903588 39.5868383, -105.0902622 39.5865865, -105.0901249 39.5862612, -105.0900451 39.5860819, -105.0899283 39.5858283, -105.0898739 39.5857117, -105.0895814 39.5851569, -105.0888764 39.5839527)\",\"properties\":{\"laneId\":1,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":2,\"geometry\":\"LINESTRING (-105.0907462 39.5878956, -105.090652 39.5875596, -105.090534 39.5871793, -105.0903457 39.5866864, -105.0902123 39.5863581, -105.0900802 39.5860572, -105.0898164 39.5855019, -105.0895409 39.5849856, -105.088922 39.5839259)\",\"properties\":{\"laneId\":2,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":3,\"geometry\":\"LINESTRING (-105.0907914 39.5878879, -105.090747 39.5877247, -105.0906498 39.5874141, -105.0906262 39.5873356, -105.0905865 39.5872922)\",\"properties\":{\"laneId\":3,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":6,\"geometry\":\"LINESTRING (-105.0910008 39.5878477, -105.0909927 39.5878181)\",\"properties\":{\"laneId\":6,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":5,\"geometry\":\"LINESTRING (-105.090959 39.5878557, -105.0909501 39.5878218)\",\"properties\":{\"laneId\":5,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":4,\"geometry\":\"LINESTRING (-105.090914 39.5878612, -105.0909051 39.5878298)\",\"properties\":{\"laneId\":4,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":10,\"geometry\":\"LINESTRING (-105.0911626 39.5880622, -105.0912043 39.5880536)\",\"properties\":{\"laneId\":10,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":4,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":8,\"geometry\":\"LINESTRING (-105.0911477 39.587995, -105.0914565 39.5879427, -105.0917937 39.5879029, -105.0922121 39.5878724, -105.0926509 39.5878748, -105.0930303 39.5879073, -105.0932697 39.5879503, -105.0937243 39.5880569, -105.0940309 39.5881258, -105.0943257 39.5881804, -105.094592 39.5882097)\",\"properties\":{\"laneId\":8,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":7,\"geometry\":\"LINESTRING (-105.0911442 39.5879589, -105.0914154 39.5879165, -105.0916346 39.5878851, -105.0918433 39.5878639, -105.0921546 39.5878547)\",\"properties\":{\"laneId\":7,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":6,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":9,\"geometry\":\"LINESTRING (-105.0911534 39.5880261, -105.091436 39.5879812, -105.0916658 39.5879507, -105.091881 39.5879277, -105.0921287 39.5878972)\",\"properties\":{\"laneId\":9,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":12,\"geometry\":\"LINESTRING (-105.0910447 39.5881948, -105.0911481 39.5886317, -105.091196 39.588862, -105.0912349 39.5890282, -105.0912722 39.5893202, -105.0913306 39.5897261, -105.0913695 39.5900324, -105.0914008 39.5903008, -105.0914893 39.5913099, -105.091527 39.5923157)\",\"properties\":{\"laneId\":12,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":5,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":13,\"geometry\":\"LINESTRING (-105.0910013 39.5881975, -105.0911059 39.5886309, -105.091144 39.5888313, -105.0911829 39.5890442, -105.0912308 39.5893169, -105.0912689 39.5895877, -105.0913005 39.5898143, -105.0913313 39.5900714, -105.0913597 39.5902968, -105.0914461 39.5913017, -105.0914756 39.592324)\",\"properties\":{\"laneId\":13,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":4,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":11,\"geometry\":\"LINESTRING (-105.0910891 39.5881859, -105.0911549 39.5884681, -105.091196 39.5886783, -105.091222 39.5888049, -105.0912401 39.5889649)\",\"properties\":{\"laneId\":11,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":14,\"geometry\":\"LINESTRING (-105.0908389 39.5882151, -105.0908478 39.5882471)\",\"properties\":{\"laneId\":14,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":15,\"geometry\":\"LINESTRING (-105.0907875 39.58822, -105.0907979 39.5882514)\",\"properties\":{\"laneId\":15,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}}]},\"connectingLanesFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":\"1-15\",\"geometry\":\"LINESTRING (-105.0907089 39.587905, -105.0907875 39.58822)\",\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":1,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"2-14\",\"geometry\":\"LINESTRING (-105.0907462 39.5878956, -105.0908389 39.5882151)\",\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":2,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"3-10\",\"geometry\":\"LINESTRING (-105.0907914 39.5878879, -105.0911626 39.5880622)\",\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":3,\"egressLaneId\":10}},{\"type\":\"Feature\",\"id\":\"8-15\",\"geometry\":\"LINESTRING (-105.0911477 39.587995, -105.0907875 39.58822)\",\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":8,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"7-6\",\"geometry\":\"LINESTRING (-105.0911442 39.5879589, -105.0910008 39.5878477)\",\"properties\":{\"ingressLaneId\":7,\"egressLaneId\":6}},{\"type\":\"Feature\",\"id\":\"9-14\",\"geometry\":\"LINESTRING (-105.0911534 39.5880261, -105.0908389 39.5882151)\",\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":9,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"12-5\",\"geometry\":\"LINESTRING (-105.0910447 39.5881948, -105.090959 39.5878557)\",\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":12,\"egressLaneId\":5}},{\"type\":\"Feature\",\"id\":\"13-4\",\"geometry\":\"LINESTRING (-105.0910013 39.5881975, -105.090914 39.5878612)\",\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":13,\"egressLaneId\":4}},{\"type\":\"Feature\",\"id\":\"11-10\",\"geometry\":\"LINESTRING (-105.0910891 39.5881859, -105.0911626 39.5880622)\",\"properties\":{\"ingressLaneId\":11,\"egressLaneId\":10}}]},\"properties\":{\"messageType\":\"MAP\",\"odeReceivedAt\":\"2024-05-03T20:58:48.748Z\",\"originIp\":\"10.11.81.12\",\"intersectionId\":12109,\"msgIssueRevision\":2,\"revision\":2,\"refPoint\":{\"latitude\":39.5880413,\"longitude\":-105.0908854,\"elevation\":1691},\"cti4501Conformant\":false,\"laneWidth\":366,\"mapSource\":\"RSU\",\"timeStamp\":\"2024-05-03T20:58:48.748Z\"}}"; + String inputProcessedMapWkt3 = "{\"mapFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":1,\"geometry\":\"LINESTRING (-105.0907089 39.587905, -105.0906245 39.5876246, -105.0905203 39.587281, -105.0904383 39.5870554, -105.0903588 39.5868383, -105.0902622 39.5865865, -105.0901249 39.5862612, -105.0900451 39.5860819, -105.0899283 39.5858283, -105.0898739 39.5857117, -105.0895814 39.5851569, -105.0888764 39.5839527)\",\"properties\":{\"laneId\":1,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":2,\"geometry\":\"LINESTRING (-105.0907462 39.5878956, -105.090652 39.5875596, -105.090534 39.5871793, -105.0903457 39.5866864, -105.0902123 39.5863581, -105.0900802 39.5860572, -105.0898164 39.5855019, -105.0895409 39.5849856, -105.088922 39.5839259)\",\"properties\":{\"laneId\":2,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":3,\"geometry\":\"LINESTRING (-105.0907914 39.5878879, -105.090747 39.5877247, -105.0906498 39.5874141, -105.0906262 39.5873356, -105.0905865 39.5872922)\",\"properties\":{\"laneId\":3,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":6,\"geometry\":\"LINESTRING (-105.0910008 39.5878477, -105.0909927 39.5878181)\",\"properties\":{\"laneId\":6,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":5,\"geometry\":\"LINESTRING (-105.090959 39.5878557, -105.0909501 39.5878218)\",\"properties\":{\"laneId\":5,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":4,\"geometry\":\"LINESTRING (-105.090914 39.5878612, -105.0909051 39.5878298)\",\"properties\":{\"laneId\":4,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":10,\"geometry\":\"LINESTRING (-105.0911626 39.5880622, -105.0912043 39.5880536)\",\"properties\":{\"laneId\":10,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":4,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":8,\"geometry\":\"LINESTRING (-105.0911477 39.587995, -105.0914565 39.5879427, -105.0917937 39.5879029, -105.0922121 39.5878724, -105.0926509 39.5878748, -105.0930303 39.5879073, -105.0932697 39.5879503, -105.0937243 39.5880569, -105.0940309 39.5881258, -105.0943257 39.5881804, -105.094592 39.5882097)\",\"properties\":{\"laneId\":8,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":7,\"geometry\":\"LINESTRING (-105.0911442 39.5879589, -105.0914154 39.5879165, -105.0916346 39.5878851, -105.0918433 39.5878639, -105.0921546 39.5878547)\",\"properties\":{\"laneId\":7,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":6,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":9,\"geometry\":\"LINESTRING (-105.0911534 39.5880261, -105.091436 39.5879812, -105.0916658 39.5879507, -105.091881 39.5879277, -105.0921287 39.5878972)\",\"properties\":{\"laneId\":9,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":12,\"geometry\":\"LINESTRING (-105.0910447 39.5881948, -105.0911481 39.5886317, -105.091196 39.588862, -105.0912349 39.5890282, -105.0912722 39.5893202, -105.0913306 39.5897261, -105.0913695 39.5900324, -105.0914008 39.5903008, -105.0914893 39.5913099, -105.091527 39.5923157)\",\"properties\":{\"laneId\":12,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":5,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":13,\"geometry\":\"LINESTRING (-105.0910013 39.5881975, -105.0911059 39.5886309, -105.091144 39.5888313, -105.0911829 39.5890442, -105.0912308 39.5893169, -105.0912689 39.5895877, -105.0913005 39.5898143, -105.0913313 39.5900714, -105.0913597 39.5902968, -105.0914461 39.5913017, -105.0914756 39.592324)\",\"properties\":{\"laneId\":13,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":4,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":11,\"geometry\":\"LINESTRING (-105.0910891 39.5881859, -105.0911549 39.5884681, -105.091196 39.5886783, -105.091222 39.5888049, -105.0912401 39.5889649)\",\"properties\":{\"laneId\":11,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":14,\"geometry\":\"LINESTRING (-105.0908389 39.5882151, -105.0908478 39.5882471)\",\"properties\":{\"laneId\":14,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":15,\"geometry\":\"LINESTRING (-105.0907875 39.58822, -105.0907979 39.5882514)\",\"properties\":{\"laneId\":15,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}}]},\"connectingLanesFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":\"1-15\",\"geometry\":\"LINESTRING (-105.0907089 39.587905, -105.0907875 39.58822)\",\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":1,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"2-14\",\"geometry\":\"LINESTRING (-105.0907462 39.5878956, -105.0908389 39.5882151)\",\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":2,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"3-10\",\"geometry\":\"LINESTRING (-105.0907914 39.5878879, -105.0911626 39.5880622)\",\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":3,\"egressLaneId\":10}},{\"type\":\"Feature\",\"id\":\"8-15\",\"geometry\":\"LINESTRING (-105.0911477 39.587995, -105.0907875 39.58822)\",\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":8,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"7-6\",\"geometry\":\"LINESTRING (-105.0911442 39.5879589, -105.0910008 39.5878477)\",\"properties\":{\"ingressLaneId\":7,\"egressLaneId\":6}},{\"type\":\"Feature\",\"id\":\"9-14\",\"geometry\":\"LINESTRING (-105.0911534 39.5880261, -105.0908389 39.5882151)\",\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":9,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"12-5\",\"geometry\":\"LINESTRING (-105.0910447 39.5881948, -105.090959 39.5878557)\",\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":12,\"egressLaneId\":5}},{\"type\":\"Feature\",\"id\":\"13-4\",\"geometry\":\"LINESTRING (-105.0910013 39.5881975, -105.090914 39.5878612)\",\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":13,\"egressLaneId\":4}},{\"type\":\"Feature\",\"id\":\"11-10\",\"geometry\":\"LINESTRING (-105.0910891 39.5881859, -105.0911626 39.5880622)\",\"properties\":{\"ingressLaneId\":11,\"egressLaneId\":10}}]},\"properties\":{\"messageType\":\"MAP\",\"odeReceivedAt\":\"2024-05-03T20:58:48.748Z\",\"originIp\":\"10.11.81.12\",\"intersectionId\":12109,\"msgIssueRevision\":2,\"revision\":2,\"refPoint\":{\"latitude\":39.5880413,\"longitude\":-105.0908854,\"elevation\":1691},\"cti4501Conformant\":false,\"laneWidth\":366,\"mapSource\":\"RSU\",\"timeStamp\":\"2024-05-03T20:59:48.748Z\"}}"; + String inputProcessedMapWkt4 = "{\"mapFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":1,\"geometry\":\"LINESTRING (-105.0907089 39.587905, -105.0906245 39.5876246, -105.0905203 39.587281, -105.0904383 39.5870554, -105.0903588 39.5868383, -105.0902622 39.5865865, -105.0901249 39.5862612, -105.0900451 39.5860819, -105.0899283 39.5858283, -105.0898739 39.5857117, -105.0895814 39.5851569, -105.0888764 39.5839527)\",\"properties\":{\"laneId\":1,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":2,\"geometry\":\"LINESTRING (-105.0907462 39.5878956, -105.090652 39.5875596, -105.090534 39.5871793, -105.0903457 39.5866864, -105.0902123 39.5863581, -105.0900802 39.5860572, -105.0898164 39.5855019, -105.0895409 39.5849856, -105.088922 39.5839259)\",\"properties\":{\"laneId\":2,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":3,\"geometry\":\"LINESTRING (-105.0907914 39.5878879, -105.090747 39.5877247, -105.0906498 39.5874141, -105.0906262 39.5873356, -105.0905865 39.5872922)\",\"properties\":{\"laneId\":3,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":1,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":2,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":6,\"geometry\":\"LINESTRING (-105.0910008 39.5878477, -105.0909927 39.5878181)\",\"properties\":{\"laneId\":6,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":5,\"geometry\":\"LINESTRING (-105.090959 39.5878557, -105.0909501 39.5878218)\",\"properties\":{\"laneId\":5,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":4,\"geometry\":\"LINESTRING (-105.090914 39.5878612, -105.0909051 39.5878298)\",\"properties\":{\"laneId\":4,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":2,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":10,\"geometry\":\"LINESTRING (-105.0911626 39.5880622, -105.0912043 39.5880536)\",\"properties\":{\"laneId\":10,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":4,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":8,\"geometry\":\"LINESTRING (-105.0911477 39.587995, -105.0914565 39.5879427, -105.0917937 39.5879029, -105.0922121 39.5878724, -105.0926509 39.5878748, -105.0930303 39.5879073, -105.0932697 39.5879503, -105.0937243 39.5880569, -105.0940309 39.5881258, -105.0943257 39.5881804, -105.094592 39.5882097)\",\"properties\":{\"laneId\":8,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":15,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":7,\"geometry\":\"LINESTRING (-105.0911442 39.5879589, -105.0914154 39.5879165, -105.0916346 39.5878851, -105.0918433 39.5878639, -105.0921546 39.5878547)\",\"properties\":{\"laneId\":7,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":6,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":9,\"geometry\":\"LINESTRING (-105.0911534 39.5880261, -105.091436 39.5879812, -105.0916658 39.5879507, -105.091881 39.5879277, -105.0921287 39.5878972)\",\"properties\":{\"laneId\":9,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":3,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":14,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":true,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":4,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":12,\"geometry\":\"LINESTRING (-105.0910447 39.5881948, -105.0911481 39.5886317, -105.091196 39.588862, -105.0912349 39.5890282, -105.0912722 39.5893202, -105.0913306 39.5897261, -105.0913695 39.5900324, -105.0914008 39.5903008, -105.0914893 39.5913099, -105.091527 39.5923157)\",\"properties\":{\"laneId\":12,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":5,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":13,\"geometry\":\"LINESTRING (-105.0910013 39.5881975, -105.0911059 39.5886309, -105.091144 39.5888313, -105.0911829 39.5890442, -105.0912308 39.5893169, -105.0912689 39.5895877, -105.0913005 39.5898143, -105.0913313 39.5900714, -105.0913597 39.5902968, -105.0914461 39.5913017, -105.0914756 39.592324)\",\"properties\":{\"laneId\":13,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":4,\"maneuver\":{\"maneuverStraightAllowed\":true,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":false,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"signalGroup\":6,\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":11,\"geometry\":\"LINESTRING (-105.0910891 39.5881859, -105.0911549 39.5884681, -105.091196 39.5886783, -105.091222 39.5888049, -105.0912401 39.5889649)\",\"properties\":{\"laneId\":11,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":0,\"ingressApproach\":5,\"ingressPath\":true,\"egressPath\":false,\"maneuvers\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false},\"connectsTo\":[{\"connectingLane\":{\"lane\":10,\"maneuver\":{\"maneuverStraightAllowed\":false,\"maneuverNoStoppingAllowed\":false,\"goWithHalt\":false,\"maneuverLeftAllowed\":false,\"maneuverUTurnAllowed\":false,\"maneuverLeftTurnOnRedAllowed\":false,\"reserved1\":false,\"maneuverRightAllowed\":true,\"maneuverLaneChangeAllowed\":false,\"yieldAllwaysRequired\":false,\"maneuverRightTurnOnRedAllowed\":false,\"caution\":false}},\"connectionID\":1}]}},{\"type\":\"Feature\",\"id\":14,\"geometry\":\"LINESTRING (-105.0908389 39.5882151, -105.0908478 39.5882471)\",\"properties\":{\"laneId\":14,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}},{\"type\":\"Feature\",\"id\":15,\"geometry\":\"LINESTRING (-105.0907875 39.58822, -105.0907979 39.5882514)\",\"properties\":{\"laneId\":15,\"laneType\":{\"vehicle\":{\"isVehicleRevocableLane\":false,\"isVehicleFlyOverLane\":false,\"permissionOnRequest\":false,\"hasIRbeaconCoverage\":false,\"restrictedToBusUse\":false,\"restrictedToTaxiUse\":false,\"restrictedFromPublicUse\":false,\"hovLaneUseOnly\":false}},\"sharedWith\":{\"busVehicleTraffic\":false,\"trackedVehicleTraffic\":false,\"individualMotorizedVehicleTraffic\":false,\"taxiVehicleTraffic\":false,\"overlappingLaneDescriptionProvided\":false,\"cyclistVehicleTraffic\":false,\"otherNonMotorizedTrafficTypes\":false,\"multipleLanesTreatedAsOneLane\":false,\"pedestrianTraffic\":false,\"pedestriansTraffic\":false},\"egressApproach\":6,\"ingressApproach\":0,\"ingressPath\":false,\"egressPath\":true}}]},\"connectingLanesFeatureCollection\":{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":\"1-15\",\"geometry\":\"LINESTRING (-105.0907089 39.587905, -105.0907875 39.58822)\",\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":1,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"2-14\",\"geometry\":\"LINESTRING (-105.0907462 39.5878956, -105.0908389 39.5882151)\",\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":2,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"3-10\",\"geometry\":\"LINESTRING (-105.0907914 39.5878879, -105.0911626 39.5880622)\",\"properties\":{\"signalGroupId\":2,\"ingressLaneId\":3,\"egressLaneId\":10}},{\"type\":\"Feature\",\"id\":\"8-15\",\"geometry\":\"LINESTRING (-105.0911477 39.587995, -105.0907875 39.58822)\",\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":8,\"egressLaneId\":15}},{\"type\":\"Feature\",\"id\":\"7-6\",\"geometry\":\"LINESTRING (-105.0911442 39.5879589, -105.0910008 39.5878477)\",\"properties\":{\"ingressLaneId\":7,\"egressLaneId\":6}},{\"type\":\"Feature\",\"id\":\"9-14\",\"geometry\":\"LINESTRING (-105.0911534 39.5880261, -105.0908389 39.5882151)\",\"properties\":{\"signalGroupId\":4,\"ingressLaneId\":9,\"egressLaneId\":14}},{\"type\":\"Feature\",\"id\":\"12-5\",\"geometry\":\"LINESTRING (-105.0910447 39.5881948, -105.090959 39.5878557)\",\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":12,\"egressLaneId\":5}},{\"type\":\"Feature\",\"id\":\"13-4\",\"geometry\":\"LINESTRING (-105.0910013 39.5881975, -105.090914 39.5878612)\",\"properties\":{\"signalGroupId\":6,\"ingressLaneId\":13,\"egressLaneId\":4}},{\"type\":\"Feature\",\"id\":\"11-10\",\"geometry\":\"LINESTRING (-105.0910891 39.5881859, -105.0911626 39.5880622)\",\"properties\":{\"ingressLaneId\":11,\"egressLaneId\":10}}]},\"properties\":{\"messageType\":\"MAP\",\"odeReceivedAt\":\"2024-05-03T20:58:48.748Z\",\"originIp\":\"10.11.81.12\",\"intersectionId\":12109,\"msgIssueRevision\":2,\"revision\":2,\"refPoint\":{\"latitude\":39.5880413,\"longitude\":-105.0908854,\"elevation\":1691},\"cti4501Conformant\":false,\"laneWidth\":366,\"mapSource\":\"RSU\",\"timeStamp\":\"2024-05-03T22:00:48.748Z\"}}"; + + String key = "{\"rsuId\":\"10.11.81.12\",\"intersectionId\":12109,\"region\":-1}"; + + @Autowired + DeduplicatorProperties props; + + + @Test + public void testTopology() { + + props = new DeduplicatorProperties(); + props.setKafkaTopicProcessedMapWKT(inputTopic); + props.setKafkaTopicDeduplicatedProcessedMapWKT(outputTopic); + + ProcessedMapWktDeduplicatorTopology processedMapDeduplicatorTopology = new ProcessedMapWktDeduplicatorTopology(props, null); + + Topology topology = processedMapDeduplicatorTopology.buildTopology(); + objectMapper.registerModule(new JavaTimeModule()); + + try (TopologyTestDriver driver = new TopologyTestDriver(topology)) { + + + TestInputTopic inputProcessedMapData = driver.createInputTopic( + inputTopic, + Serdes.String().serializer(), + Serdes.String().serializer()); + + + TestOutputTopic> outputProcessedMapData = driver.createOutputTopic( + outputTopic, + Serdes.String().deserializer(), + JsonSerdes.ProcessedMapWKT().deserializer()); + + inputProcessedMapData.pipeInput(key, inputProcessedMapWkt1); + inputProcessedMapData.pipeInput(key, inputProcessedMapWkt2); + inputProcessedMapData.pipeInput(key, inputProcessedMapWkt3); + inputProcessedMapData.pipeInput(key, inputProcessedMapWkt4); + + List>> mapDeduplicatorResults = outputProcessedMapData.readKeyValuesToList(); + + // validate that only 3 messages make it through + assertEquals(3, mapDeduplicatorResults.size()); + + ProcessedMap map1 = objectMapper.readValue(inputProcessedMapWkt1, typeReference); + ProcessedMap map2 = objectMapper.readValue(inputProcessedMapWkt2, typeReference); + ProcessedMap map4 = objectMapper.readValue(inputProcessedMapWkt4, typeReference); + + assertEquals(map1.getProperties().getOdeReceivedAt(), mapDeduplicatorResults.get(0).value.getProperties().getOdeReceivedAt()); + assertEquals(map2.getProperties().getOdeReceivedAt(), mapDeduplicatorResults.get(1).value.getProperties().getOdeReceivedAt()); + assertEquals(map4.getProperties().getOdeReceivedAt(), mapDeduplicatorResults.get(2).value.getProperties().getOdeReceivedAt()); + + + + } catch (JsonMappingException e) { + e.printStackTrace(); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/ProcessedSpatDeduplicatorTopologyTest.java b/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/ProcessedSpatDeduplicatorTopologyTest.java new file mode 100644 index 0000000..96fd914 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/ProcessedSpatDeduplicatorTopologyTest.java @@ -0,0 +1,106 @@ +package deduplicator; + +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.TestInputTopic; +import org.apache.kafka.streams.TestOutputTopic; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.TopologyTestDriver; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.topologies.ProcessedSpatDeduplicatorTopology; +import us.dot.its.jpo.geojsonconverter.pojos.spat.ProcessedSpat; +import us.dot.its.jpo.geojsonconverter.serialization.JsonSerdes; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + + +public class ProcessedSpatDeduplicatorTopologyTest { + + String inputTopic = "topic.ProcessedSpat"; + String outputTopic = "topic.DeduplicatedProcessedSpat"; + + + TypeReference typeReference = new TypeReference<>(){}; + ObjectMapper objectMapper = new ObjectMapper(); + + String inputProcessedSpat1 = "{\"schemaVersion\":-1,\"messageType\":\"SPAT\",\"odeReceivedAt\":\"2024-11-01T15:49:05.278Z\",\"originIp\":\"10.164.6.16\",\"name\":\"N State St & E Center St\",\"intersectionId\":6311,\"cti4501Conformant\":false,\"validationMessages\":[{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].id.region: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].id\",\"schemaPath\":\"#/$defs/J2735IntersectionReferenceID/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.maxEndTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"}],\"revision\":102,\"status\":{\"manualControlIsEnabled\":false,\"stopTimeIsActivated\":false,\"failureFlash\":false,\"preemptIsActive\":false,\"signalPriorityIsActive\":false,\"fixedTimeOperation\":false,\"trafficDependentOperation\":false,\"standbyOperation\":false,\"failureMode\":false,\"off\":false,\"recentMAPmessageUpdate\":true,\"recentChangeInMAPassignedLanesIDsUsed\":true,\"noValidMAPisAvailableAtThisTime\":false,\"noValidSPATisAvailableAtThisTime\":false},\"utcTimeStamp\":\"2024-11-01T15:49:05.278Z\",\"enabledLanes\":[],\"states\":[{\"signalGroup\":1,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:04.237Z\",\"maxEndTime\":\"2024-11-01T15:50:04.237Z\"}}]},{\"signalGroup\":2,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:08.337Z\",\"maxEndTime\":\"2024-11-01T15:49:08.337Z\"}}]},{\"signalGroup\":3,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:24.237Z\",\"maxEndTime\":\"2024-11-01T15:50:24.237Z\"}}]},{\"signalGroup\":4,\"stateTimeSpeed\":[{\"eventState\":\"PERMISSIVE_CLEARANCE\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:06.137Z\",\"maxEndTime\":\"2024-11-01T15:49:06.137Z\"}}]},{\"signalGroup\":5,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:08.337Z\",\"maxEndTime\":\"2024-11-01T15:49:08.337Z\"}}]},{\"signalGroup\":6,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:44.237Z\",\"maxEndTime\":\"2024-11-01T15:49:44.237Z\"}}]},{\"signalGroup\":7,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:05.237Z\"}}]},{\"signalGroup\":8,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:24.237Z\",\"maxEndTime\":\"2024-11-01T15:50:24.237Z\"}}]}]}"; + String inputProcessedSpat2 = "{\"schemaVersion\":-1,\"messageType\":\"SPAT\",\"odeReceivedAt\":\"2024-11-01T15:49:05.278Z\",\"originIp\":\"10.164.6.16\",\"name\":\"N State St & E Center St\",\"intersectionId\":6311,\"cti4501Conformant\":false,\"validationMessages\":[{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].id.region: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].id\",\"schemaPath\":\"#/$defs/J2735IntersectionReferenceID/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.maxEndTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"}],\"revision\":102,\"status\":{\"manualControlIsEnabled\":false,\"stopTimeIsActivated\":false,\"failureFlash\":false,\"preemptIsActive\":false,\"signalPriorityIsActive\":false,\"fixedTimeOperation\":false,\"trafficDependentOperation\":false,\"standbyOperation\":false,\"failureMode\":false,\"off\":false,\"recentMAPmessageUpdate\":true,\"recentChangeInMAPassignedLanesIDsUsed\":true,\"noValidMAPisAvailableAtThisTime\":false,\"noValidSPATisAvailableAtThisTime\":false},\"utcTimeStamp\":\"2024-11-01T15:49:05.378Z\",\"enabledLanes\":[],\"states\":[{\"signalGroup\":1,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:04.237Z\",\"maxEndTime\":\"2024-11-01T15:50:04.237Z\"}}]},{\"signalGroup\":2,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:08.337Z\",\"maxEndTime\":\"2024-11-01T15:49:08.337Z\"}}]},{\"signalGroup\":3,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:24.237Z\",\"maxEndTime\":\"2024-11-01T15:50:24.237Z\"}}]},{\"signalGroup\":4,\"stateTimeSpeed\":[{\"eventState\":\"PERMISSIVE_CLEARANCE\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:06.137Z\",\"maxEndTime\":\"2024-11-01T15:49:06.137Z\"}}]},{\"signalGroup\":5,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:08.337Z\",\"maxEndTime\":\"2024-11-01T15:49:08.337Z\"}}]},{\"signalGroup\":6,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:44.237Z\",\"maxEndTime\":\"2024-11-01T15:49:44.237Z\"}}]},{\"signalGroup\":7,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:05.237Z\"}}]},{\"signalGroup\":8,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:24.237Z\",\"maxEndTime\":\"2024-11-01T15:50:24.237Z\"}}]}]}"; + String inputProcessedSpat3 = "{\"schemaVersion\":-1,\"messageType\":\"SPAT\",\"odeReceivedAt\":\"2024-11-01T15:49:05.278Z\",\"originIp\":\"10.164.6.16\",\"name\":\"N State St & E Center St\",\"intersectionId\":6311,\"cti4501Conformant\":false,\"validationMessages\":[{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].id.region: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].id\",\"schemaPath\":\"#/$defs/J2735IntersectionReferenceID/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.maxEndTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"}],\"revision\":102,\"status\":{\"manualControlIsEnabled\":false,\"stopTimeIsActivated\":false,\"failureFlash\":false,\"preemptIsActive\":false,\"signalPriorityIsActive\":false,\"fixedTimeOperation\":false,\"trafficDependentOperation\":false,\"standbyOperation\":false,\"failureMode\":false,\"off\":false,\"recentMAPmessageUpdate\":true,\"recentChangeInMAPassignedLanesIDsUsed\":true,\"noValidMAPisAvailableAtThisTime\":false,\"noValidSPATisAvailableAtThisTime\":false},\"utcTimeStamp\":\"2024-11-01T15:50:05.478Z\",\"enabledLanes\":[],\"states\":[{\"signalGroup\":1,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:04.237Z\",\"maxEndTime\":\"2024-11-01T15:50:04.237Z\"}}]},{\"signalGroup\":2,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:08.337Z\",\"maxEndTime\":\"2024-11-01T15:49:08.337Z\"}}]},{\"signalGroup\":3,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:24.237Z\",\"maxEndTime\":\"2024-11-01T15:50:24.237Z\"}}]},{\"signalGroup\":4,\"stateTimeSpeed\":[{\"eventState\":\"PERMISSIVE_CLEARANCE\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:06.137Z\",\"maxEndTime\":\"2024-11-01T15:49:06.137Z\"}}]},{\"signalGroup\":5,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:08.337Z\",\"maxEndTime\":\"2024-11-01T15:49:08.337Z\"}}]},{\"signalGroup\":6,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:44.237Z\",\"maxEndTime\":\"2024-11-01T15:49:44.237Z\"}}]},{\"signalGroup\":7,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:05.237Z\"}}]},{\"signalGroup\":8,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:24.237Z\",\"maxEndTime\":\"2024-11-01T15:50:24.237Z\"}}]}]}"; + String inputProcessedSpat4 = "{\"schemaVersion\":-1,\"messageType\":\"SPAT\",\"odeReceivedAt\":\"2024-11-01T15:49:05.278Z\",\"originIp\":\"10.164.6.16\",\"name\":\"N State St & E Center St\",\"intersectionId\":6311,\"cti4501Conformant\":false,\"validationMessages\":[{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].id.region: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].id\",\"schemaPath\":\"#/$defs/J2735IntersectionReferenceID/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.maxEndTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"}],\"revision\":102,\"status\":{\"manualControlIsEnabled\":false,\"stopTimeIsActivated\":false,\"failureFlash\":false,\"preemptIsActive\":false,\"signalPriorityIsActive\":false,\"fixedTimeOperation\":false,\"trafficDependentOperation\":false,\"standbyOperation\":false,\"failureMode\":false,\"off\":false,\"recentMAPmessageUpdate\":true,\"recentChangeInMAPassignedLanesIDsUsed\":true,\"noValidMAPisAvailableAtThisTime\":false,\"noValidSPATisAvailableAtThisTime\":false},\"utcTimeStamp\":\"2024-11-01T15:50:05.578Z\",\"enabledLanes\":[],\"states\":[{\"signalGroup\":1,\"stateTimeSpeed\":[{\"eventState\":\"PROTECTED_MOVEMENT_ALLOWED\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:04.237Z\",\"maxEndTime\":\"2024-11-01T15:50:04.237Z\"}}]},{\"signalGroup\":2,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:08.337Z\",\"maxEndTime\":\"2024-11-01T15:49:08.337Z\"}}]},{\"signalGroup\":3,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:24.237Z\",\"maxEndTime\":\"2024-11-01T15:50:24.237Z\"}}]},{\"signalGroup\":4,\"stateTimeSpeed\":[{\"eventState\":\"PERMISSIVE_CLEARANCE\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:06.137Z\",\"maxEndTime\":\"2024-11-01T15:49:06.137Z\"}}]},{\"signalGroup\":5,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:08.337Z\",\"maxEndTime\":\"2024-11-01T15:49:08.337Z\"}}]},{\"signalGroup\":6,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:44.237Z\",\"maxEndTime\":\"2024-11-01T15:49:44.237Z\"}}]},{\"signalGroup\":7,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:05.237Z\"}}]},{\"signalGroup\":8,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:24.237Z\",\"maxEndTime\":\"2024-11-01T15:50:24.237Z\"}}]}]}"; + String inputProcessedSpat5 = "{\"schemaVersion\":-1,\"messageType\":\"SPAT\",\"odeReceivedAt\":\"2024-11-01T15:49:05.278Z\",\"originIp\":\"10.164.6.16\",\"name\":\"N State St & E Center St\",\"intersectionId\":6311,\"cti4501Conformant\":false,\"validationMessages\":[{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].id.region: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].id\",\"schemaPath\":\"#/$defs/J2735IntersectionReferenceID/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.maxEndTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"}],\"revision\":102,\"status\":{\"manualControlIsEnabled\":false,\"stopTimeIsActivated\":false,\"failureFlash\":false,\"preemptIsActive\":false,\"signalPriorityIsActive\":false,\"fixedTimeOperation\":false,\"trafficDependentOperation\":false,\"standbyOperation\":false,\"failureMode\":false,\"off\":false,\"recentMAPmessageUpdate\":true,\"recentChangeInMAPassignedLanesIDsUsed\":true,\"noValidMAPisAvailableAtThisTime\":false,\"noValidSPATisAvailableAtThisTime\":false},\"utcTimeStamp\":\"2024-11-01T15:50:05.678Z\",\"enabledLanes\":[],\"states\":[{\"signalGroup\":1,\"stateTimeSpeed\":[{\"eventState\":\"PROTECTED_MOVEMENT_ALLOWED\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:04.237Z\",\"maxEndTime\":\"2024-11-01T15:50:04.237Z\"}}]},{\"signalGroup\":2,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:08.337Z\",\"maxEndTime\":\"2024-11-01T15:49:08.337Z\"}}]},{\"signalGroup\":3,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:24.237Z\",\"maxEndTime\":\"2024-11-01T15:50:24.237Z\"}}]},{\"signalGroup\":4,\"stateTimeSpeed\":[{\"eventState\":\"PERMISSIVE_CLEARANCE\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:06.137Z\",\"maxEndTime\":\"2024-11-01T15:49:06.137Z\"}}]},{\"signalGroup\":5,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:08.337Z\",\"maxEndTime\":\"2024-11-01T15:49:08.337Z\"}}]},{\"signalGroup\":6,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:44.237Z\",\"maxEndTime\":\"2024-11-01T15:49:44.237Z\"}}]},{\"signalGroup\":7,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:05.237Z\"}}]}]}"; + String inputProcessedSpat6 = "{\"schemaVersion\":-1,\"messageType\":\"SPAT\",\"odeReceivedAt\":\"2024-11-01T15:49:05.278Z\",\"originIp\":\"10.164.6.16\",\"name\":\"N State St & E Center St\",\"intersectionId\":6311,\"cti4501Conformant\":false,\"validationMessages\":[{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].id.region: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].id\",\"schemaPath\":\"#/$defs/J2735IntersectionReferenceID/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[0].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[1].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[2].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[3].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[4].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[5].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.maxEndTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[6].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing.startTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"},{\"message\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing.nextTime: is missing but it is required\",\"jsonPath\":\"$.payload.data.intersectionStateList.intersectionStatelist[0].states.movementList[7].state_time_speed.movementEventList[0].timing\",\"schemaPath\":\"#/$defs/J2735TimeChangeDetails/required\"}],\"revision\":102,\"status\":{\"manualControlIsEnabled\":false,\"stopTimeIsActivated\":false,\"failureFlash\":false,\"preemptIsActive\":false,\"signalPriorityIsActive\":false,\"fixedTimeOperation\":false,\"trafficDependentOperation\":false,\"standbyOperation\":false,\"failureMode\":false,\"off\":false,\"recentMAPmessageUpdate\":true,\"recentChangeInMAPassignedLanesIDsUsed\":true,\"noValidMAPisAvailableAtThisTime\":false,\"noValidSPATisAvailableAtThisTime\":false},\"utcTimeStamp\":\"2024-11-01T15:50:05.778Z\",\"enabledLanes\":[],\"states\":[{\"signalGroup\":1,\"stateTimeSpeed\":[{\"eventState\":\"PROTECTED_MOVEMENT_ALLOWED\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:04.237Z\",\"maxEndTime\":\"2024-11-01T15:50:04.237Z\"}}]},{\"signalGroup\":2,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:08.337Z\",\"maxEndTime\":\"2024-11-01T15:49:08.337Z\"}}]},{\"signalGroup\":3,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:50:24.237Z\",\"maxEndTime\":\"2024-11-01T15:50:24.237Z\"}}]},{\"signalGroup\":4,\"stateTimeSpeed\":[{\"eventState\":\"PERMISSIVE_CLEARANCE\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:06.137Z\",\"maxEndTime\":\"2024-11-01T15:49:06.137Z\"}}]},{\"signalGroup\":5,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:08.337Z\",\"maxEndTime\":\"2024-11-01T15:49:08.337Z\"}}]},{\"signalGroup\":6,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:44.237Z\",\"maxEndTime\":\"2024-11-01T15:49:44.237Z\"}}]},{\"signalGroup\":9,\"stateTimeSpeed\":[{\"eventState\":\"STOP_AND_REMAIN\",\"timing\":{\"minEndTime\":\"2024-11-01T15:49:05.237Z\"}}]}]}"; + + String key = "{\"rsuId\": \"10.164.6.16\", \"intersectionId\": 6311, \"region\": -1}"; + + @Autowired + DeduplicatorProperties props; + + + @Test + public void testTopology() { + + props = new DeduplicatorProperties(); + props.setKafkaTopicProcessedSpat(inputTopic); + props.setKafkaTopicDeduplicatedProcessedSpat(outputTopic); + + ProcessedSpatDeduplicatorTopology processedSpatDeduplicatorTopology = new ProcessedSpatDeduplicatorTopology(props, null); + + Topology topology = processedSpatDeduplicatorTopology.buildTopology(); + objectMapper.registerModule(new JavaTimeModule()); + + try (TopologyTestDriver driver = new TopologyTestDriver(topology)) { + + + TestInputTopic inputProcessedSpatData = driver.createInputTopic( + inputTopic, + Serdes.String().serializer(), + Serdes.String().serializer()); + + + TestOutputTopic outputProcessedSpatData = driver.createOutputTopic( + outputTopic, + Serdes.String().deserializer(), + JsonSerdes.ProcessedSpat().deserializer()); + + inputProcessedSpatData.pipeInput(key, inputProcessedSpat1); + inputProcessedSpatData.pipeInput(key, inputProcessedSpat2); + inputProcessedSpatData.pipeInput(key, inputProcessedSpat3); + inputProcessedSpatData.pipeInput(key, inputProcessedSpat4); + inputProcessedSpatData.pipeInput(key, inputProcessedSpat5); + inputProcessedSpatData.pipeInput(key, inputProcessedSpat6); + + List> spatDeduplicatorResults = outputProcessedSpatData.readKeyValuesToList(); + + // validate that only 5 messages make it through + assertEquals(5, spatDeduplicatorResults.size()); + + ProcessedSpat spat1 = objectMapper.readValue(inputProcessedSpat1, typeReference); + ProcessedSpat spat3 = objectMapper.readValue(inputProcessedSpat3, typeReference); // forwarded because the time on the message changes by 1 minute + ProcessedSpat spat4 = objectMapper.readValue(inputProcessedSpat4, typeReference); // forwarded because the signal state changes + ProcessedSpat spat5 = objectMapper.readValue(inputProcessedSpat5, typeReference); // forwarded because the number of signal groups changed + ProcessedSpat spat6 = objectMapper.readValue(inputProcessedSpat6, typeReference); // forwarded because a signal group ID changed + + assertEquals(spat1.getUtcTimeStamp(), spatDeduplicatorResults.get(0).value.getUtcTimeStamp()); + assertEquals(spat3.getUtcTimeStamp(), spatDeduplicatorResults.get(1).value.getUtcTimeStamp()); + assertEquals(spat4.getUtcTimeStamp(), spatDeduplicatorResults.get(2).value.getUtcTimeStamp()); + assertEquals(spat5.getUtcTimeStamp(), spatDeduplicatorResults.get(3).value.getUtcTimeStamp()); + assertEquals(spat6.getUtcTimeStamp(), spatDeduplicatorResults.get(4).value.getUtcTimeStamp()); + + + } catch (JsonMappingException e) { + e.printStackTrace(); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/TimDeduplicatorTopologyTest.java b/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/TimDeduplicatorTopologyTest.java new file mode 100644 index 0000000..164dd22 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/test/java/deduplicator/TimDeduplicatorTopologyTest.java @@ -0,0 +1,89 @@ +package deduplicator; + +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.TestInputTopic; +import org.apache.kafka.streams.TestOutputTopic; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.TopologyTestDriver; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import us.dot.its.jpo.deduplicator.DeduplicatorProperties; +import us.dot.its.jpo.deduplicator.deduplicator.topologies.TimDeduplicatorTopology; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + + +public class TimDeduplicatorTopologyTest { + + String inputTopic = "topic.OdeTimJson"; + String outputTopic = "topic.DeduplicatedOdeTimJson"; + + ObjectMapper objectMapper = new ObjectMapper(); + + //Original Message + String inputTim1 = "{\"metadata\":{\"securityResultCode\":\"success\",\"recordGeneratedBy\":\"RSU\",\"schemaVersion\":\"6\",\"odePacketID\":\"\",\"sanitized\":\"false\",\"recordType\":\"timMsg\",\"recordGeneratedAt\":\"\",\"maxDurationTime\":\"0\",\"odeTimStartDateTime\":\"\",\"receivedMessageDetails\":\"\",\"payloadType\":\"us.dot.its.jpo.ode.model.OdeTimPayload\",\"serialId\":{\"recordId\":\"0\",\"serialNumber\":\"0\",\"streamId\":\"d052115a-5289-4da3-bc9f-12edca1d2c46\",\"bundleSize\":\"1\",\"bundleId\":\"0\"},\"logFileName\":\"\",\"odeReceivedAt\":\"2024-07-23T18:33:17.328Z\",\"originIp\":\"10.16.28.54\"},\"payload\":{\"data\":{\"MessageFrame\":{\"messageId\":\"31\",\"value\":{\"TravelerInformation\":{\"timeStamp\":\"264703\",\"packetID\":\"0350C30EB1A83736D8\",\"urlB\":\"null\",\"dataFrames\":{\"TravelerDataFrame\":{\"durationTime\":\"30\",\"regions\":{\"GeographicalPath\":{\"closedPath\":{\"false\":\"\"},\"anchor\":{\"lat\":\"395658598\",\"long\":\"-1050401840\"},\"name\":\"I_CO-470_RSU_172.16.28.116\",\"laneWidth\":\"5000\",\"directionality\":{\"both\":\"\"},\"description\":{\"path\":{\"offset\":{\"ll\":{\"nodes\":{\"NodeLL\":[{\"delta\":{\"node-LL1\":{\"lon\":\"1527\",\"lat\":\"-659\"}}},{\"delta\":{\"node-LL2\":{\"lon\":\"5545\",\"lat\":\"-2394\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"9493\",\"lat\":\"-2736\"}}},{\"delta\":{\"node-LL2\":{\"lon\":\"7465\",\"lat\":\"-1304\"}}},{\"delta\":{\"node-LL4\":{\"lon\":\"34464\",\"lat\":\"-4324\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"9994\",\"lat\":\"-1119\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"20051\",\"lat\":\"-2246\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"31738\",\"lat\":\"-1775\"}}},{\"delta\":{\"node-LL2\":{\"lon\":\"5744\",\"lat\":\"-315\"}}}]}}},\"scale\":\"0\"}},\"id\":{\"id\":\"0\",\"region\":\"0\"},\"direction\":\"0000110000000000\"}},\"startYear\":\"2024\",\"notUsed2\":\"0\",\"msgId\":{\"roadSignID\":{\"viewAngle\":\"1111111111111111\",\"mutcdCode\":{\"warning\":\"\"},\"position\":{\"lat\":\"395658598\",\"long\":\"-1050401840\"}}},\"notUsed3\":\"0\",\"notUsed1\":\"0\",\"priority\":\"5\",\"content\":{\"workZone\":{\"SEQUENCE\":{\"item\":{\"itis\":\"1025\"}}}},\"url\":\"null\",\"notUsed\":\"0\",\"frameType\":{\"advisory\":\"\"},\"startTime\":\"277110\"}},\"msgCnt\":\"1\"}}}},\"dataType\":\"TravelerInformation\"}}"; + + // Shifted Forward .1 seconds - Should be deduplicated + String inputTim2 = "{\"metadata\":{\"securityResultCode\":\"success\",\"recordGeneratedBy\":\"RSU\",\"schemaVersion\":\"6\",\"odePacketID\":\"\",\"sanitized\":\"false\",\"recordType\":\"timMsg\",\"recordGeneratedAt\":\"\",\"maxDurationTime\":\"0\",\"odeTimStartDateTime\":\"\",\"receivedMessageDetails\":\"\",\"payloadType\":\"us.dot.its.jpo.ode.model.OdeTimPayload\",\"serialId\":{\"recordId\":\"0\",\"serialNumber\":\"0\",\"streamId\":\"d052115a-5289-4da3-bc9f-12edca1d2c46\",\"bundleSize\":\"1\",\"bundleId\":\"0\"},\"logFileName\":\"\",\"odeReceivedAt\":\"2024-07-23T18:33:17.428Z\",\"originIp\":\"10.16.28.54\"},\"payload\":{\"data\":{\"MessageFrame\":{\"messageId\":\"31\",\"value\":{\"TravelerInformation\":{\"timeStamp\":\"264703\",\"packetID\":\"0350C30EB1A83736D8\",\"urlB\":\"null\",\"dataFrames\":{\"TravelerDataFrame\":{\"durationTime\":\"30\",\"regions\":{\"GeographicalPath\":{\"closedPath\":{\"false\":\"\"},\"anchor\":{\"lat\":\"395658598\",\"long\":\"-1050401840\"},\"name\":\"I_CO-470_RSU_172.16.28.116\",\"laneWidth\":\"5000\",\"directionality\":{\"both\":\"\"},\"description\":{\"path\":{\"offset\":{\"ll\":{\"nodes\":{\"NodeLL\":[{\"delta\":{\"node-LL1\":{\"lon\":\"1527\",\"lat\":\"-659\"}}},{\"delta\":{\"node-LL2\":{\"lon\":\"5545\",\"lat\":\"-2394\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"9493\",\"lat\":\"-2736\"}}},{\"delta\":{\"node-LL2\":{\"lon\":\"7465\",\"lat\":\"-1304\"}}},{\"delta\":{\"node-LL4\":{\"lon\":\"34464\",\"lat\":\"-4324\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"9994\",\"lat\":\"-1119\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"20051\",\"lat\":\"-2246\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"31738\",\"lat\":\"-1775\"}}},{\"delta\":{\"node-LL2\":{\"lon\":\"5744\",\"lat\":\"-315\"}}}]}}},\"scale\":\"0\"}},\"id\":{\"id\":\"0\",\"region\":\"0\"},\"direction\":\"0000110000000000\"}},\"startYear\":\"2024\",\"notUsed2\":\"0\",\"msgId\":{\"roadSignID\":{\"viewAngle\":\"1111111111111111\",\"mutcdCode\":{\"warning\":\"\"},\"position\":{\"lat\":\"395658598\",\"long\":\"-1050401840\"}}},\"notUsed3\":\"0\",\"notUsed1\":\"0\",\"priority\":\"5\",\"content\":{\"workZone\":{\"SEQUENCE\":{\"item\":{\"itis\":\"1025\"}}}},\"url\":\"null\",\"notUsed\":\"0\",\"frameType\":{\"advisory\":\"\"},\"startTime\":\"277110\"}},\"msgCnt\":\"1\"}}}},\"dataType\":\"TravelerInformation\"}}"; + + // Shifted Forward 1 hour Should be allowed to pass through + String inputTim3 = "{\"metadata\":{\"securityResultCode\":\"success\",\"recordGeneratedBy\":\"RSU\",\"schemaVersion\":\"6\",\"odePacketID\":\"\",\"sanitized\":\"false\",\"recordType\":\"timMsg\",\"recordGeneratedAt\":\"\",\"maxDurationTime\":\"0\",\"odeTimStartDateTime\":\"\",\"receivedMessageDetails\":\"\",\"payloadType\":\"us.dot.its.jpo.ode.model.OdeTimPayload\",\"serialId\":{\"recordId\":\"0\",\"serialNumber\":\"0\",\"streamId\":\"d052115a-5289-4da3-bc9f-12edca1d2c46\",\"bundleSize\":\"1\",\"bundleId\":\"0\"},\"logFileName\":\"\",\"odeReceivedAt\":\"2024-07-23T19:33:17.428Z\",\"originIp\":\"10.16.28.54\"},\"payload\":{\"data\":{\"MessageFrame\":{\"messageId\":\"31\",\"value\":{\"TravelerInformation\":{\"timeStamp\":\"264703\",\"packetID\":\"0350C30EB1A83736D8\",\"urlB\":\"null\",\"dataFrames\":{\"TravelerDataFrame\":{\"durationTime\":\"30\",\"regions\":{\"GeographicalPath\":{\"closedPath\":{\"false\":\"\"},\"anchor\":{\"lat\":\"395658598\",\"long\":\"-1050401840\"},\"name\":\"I_CO-470_RSU_172.16.28.116\",\"laneWidth\":\"5000\",\"directionality\":{\"both\":\"\"},\"description\":{\"path\":{\"offset\":{\"ll\":{\"nodes\":{\"NodeLL\":[{\"delta\":{\"node-LL1\":{\"lon\":\"1527\",\"lat\":\"-659\"}}},{\"delta\":{\"node-LL2\":{\"lon\":\"5545\",\"lat\":\"-2394\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"9493\",\"lat\":\"-2736\"}}},{\"delta\":{\"node-LL2\":{\"lon\":\"7465\",\"lat\":\"-1304\"}}},{\"delta\":{\"node-LL4\":{\"lon\":\"34464\",\"lat\":\"-4324\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"9994\",\"lat\":\"-1119\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"20051\",\"lat\":\"-2246\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"31738\",\"lat\":\"-1775\"}}},{\"delta\":{\"node-LL2\":{\"lon\":\"5744\",\"lat\":\"-315\"}}}]}}},\"scale\":\"0\"}},\"id\":{\"id\":\"0\",\"region\":\"0\"},\"direction\":\"0000110000000000\"}},\"startYear\":\"2024\",\"notUsed2\":\"0\",\"msgId\":{\"roadSignID\":{\"viewAngle\":\"1111111111111111\",\"mutcdCode\":{\"warning\":\"\"},\"position\":{\"lat\":\"395658598\",\"long\":\"-1050401840\"}}},\"notUsed3\":\"0\",\"notUsed1\":\"0\",\"priority\":\"5\",\"content\":{\"workZone\":{\"SEQUENCE\":{\"item\":{\"itis\":\"1025\"}}}},\"url\":\"null\",\"notUsed\":\"0\",\"frameType\":{\"advisory\":\"\"},\"startTime\":\"277110\"}},\"msgCnt\":\"1\"}}}},\"dataType\":\"TravelerInformation\"}}"; + + // Has a different payload ID. Should be allowed through + String inputTim4 = "{\"metadata\":{\"securityResultCode\":\"success\",\"recordGeneratedBy\":\"RSU\",\"schemaVersion\":\"6\",\"odePacketID\":\"\",\"sanitized\":\"false\",\"recordType\":\"timMsg\",\"recordGeneratedAt\":\"\",\"maxDurationTime\":\"0\",\"odeTimStartDateTime\":\"\",\"receivedMessageDetails\":\"\",\"payloadType\":\"us.dot.its.jpo.ode.model.OdeTimPayload\",\"serialId\":{\"recordId\":\"0\",\"serialNumber\":\"0\",\"streamId\":\"d052115a-5289-4da3-bc9f-12edca1d2c46\",\"bundleSize\":\"1\",\"bundleId\":\"0\"},\"logFileName\":\"\",\"odeReceivedAt\":\"2024-07-23T19:34:17.328Z\",\"originIp\":\"10.16.28.54\"},\"payload\":{\"data\":{\"MessageFrame\":{\"messageId\":\"31\",\"value\":{\"TravelerInformation\":{\"timeStamp\":\"264703\",\"packetID\":\"0350C30EB1A83736D9\",\"urlB\":\"null\",\"dataFrames\":{\"TravelerDataFrame\":{\"durationTime\":\"30\",\"regions\":{\"GeographicalPath\":{\"closedPath\":{\"false\":\"\"},\"anchor\":{\"lat\":\"395658598\",\"long\":\"-1050401840\"},\"name\":\"I_CO-470_RSU_172.16.28.116\",\"laneWidth\":\"10000\",\"directionality\":{\"both\":\"\"},\"description\":{\"path\":{\"offset\":{\"ll\":{\"nodes\":{\"NodeLL\":[{\"delta\":{\"node-LL1\":{\"lon\":\"1527\",\"lat\":\"-659\"}}},{\"delta\":{\"node-LL2\":{\"lon\":\"5545\",\"lat\":\"-2394\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"9493\",\"lat\":\"-2736\"}}},{\"delta\":{\"node-LL2\":{\"lon\":\"7465\",\"lat\":\"-1304\"}}},{\"delta\":{\"node-LL4\":{\"lon\":\"34464\",\"lat\":\"-4324\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"9994\",\"lat\":\"-1119\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"20051\",\"lat\":\"-2246\"}}},{\"delta\":{\"node-LL3\":{\"lon\":\"31738\",\"lat\":\"-1775\"}}},{\"delta\":{\"node-LL2\":{\"lon\":\"5744\",\"lat\":\"-315\"}}}]}}},\"scale\":\"0\"}},\"id\":{\"id\":\"0\",\"region\":\"0\"},\"direction\":\"0000110000000000\"}},\"startYear\":\"2024\",\"notUsed2\":\"0\",\"msgId\":{\"roadSignID\":{\"viewAngle\":\"1111111111111111\",\"mutcdCode\":{\"warning\":\"\"},\"position\":{\"lat\":\"395658598\",\"long\":\"-1050401840\"}}},\"notUsed3\":\"0\",\"notUsed1\":\"0\",\"priority\":\"5\",\"content\":{\"workZone\":{\"SEQUENCE\":{\"item\":{\"itis\":\"1025\"}}}},\"url\":\"null\",\"notUsed\":\"0\",\"frameType\":{\"advisory\":\"\"},\"startTime\":\"277110\"}},\"msgCnt\":\"1\"}}}},\"dataType\":\"TravelerInformation\"}}"; + + @Autowired + DeduplicatorProperties props; + + @Test + public void testTopology() { + + props = new DeduplicatorProperties(); + props.setKafkaTopicOdeTimJson(inputTopic); + props.setKafkaTopicDeduplicatedOdeTimJson(outputTopic); + + TimDeduplicatorTopology TimDeduplicatorTopology = new TimDeduplicatorTopology(props, null); + + Topology topology = TimDeduplicatorTopology.buildTopology(); + objectMapper.registerModule(new JavaTimeModule()); + + try (TopologyTestDriver driver = new TopologyTestDriver(topology)) { + + + TestInputTopic inputTimData = driver.createInputTopic( + inputTopic, + Serdes.Void().serializer(), + Serdes.String().serializer()); + + + TestOutputTopic outputTimData = driver.createOutputTopic( + outputTopic, + Serdes.String().deserializer(), + Serdes.String().deserializer()); + + inputTimData.pipeInput(null, inputTim1); + inputTimData.pipeInput(null, inputTim2); + inputTimData.pipeInput(null, inputTim3); + inputTimData.pipeInput(null, inputTim4); + + List> timDeduplicatedResults = outputTimData.readKeyValuesToList(); + + // validate that only 3 messages make it through + assertEquals(3, timDeduplicatedResults.size()); + inputTim1 = inputTim1.strip(); + + assertEquals(inputTim1.replace(" ", ""), timDeduplicatedResults.get(0).value.replace(" ", "")); + assertEquals(inputTim3.replace(" ", ""), timDeduplicatedResults.get(1).value.replace(" ", "")); + assertEquals(inputTim4.replace(" ", ""), timDeduplicatedResults.get(2).value.replace(" ", "")); + + }catch(Exception e){ + e.printStackTrace(); + } + } +} diff --git a/jpo-deduplicator/jpo-deduplicator/src/test/resources/application-testConfig.yaml b/jpo-deduplicator/jpo-deduplicator/src/test/resources/application-testConfig.yaml new file mode 100644 index 0000000..4f102d0 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/test/resources/application-testConfig.yaml @@ -0,0 +1 @@ +spring.kafka.bootstrap-servers: localhost:10093 \ No newline at end of file diff --git a/jpo-deduplicator/jpo-deduplicator/src/test/resources/logback-test.xml b/jpo-deduplicator/jpo-deduplicator/src/test/resources/logback-test.xml new file mode 100644 index 0000000..cd07f49 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/src/test/resources/logback-test.xml @@ -0,0 +1,11 @@ + + + + %msg%n + + + + + + + \ No newline at end of file diff --git a/jpo-deduplicator/jpo-deduplicator/target/classes/META-INF/build-info.properties b/jpo-deduplicator/jpo-deduplicator/target/classes/META-INF/build-info.properties new file mode 100644 index 0000000..b9956a7 --- /dev/null +++ b/jpo-deduplicator/jpo-deduplicator/target/classes/META-INF/build-info.properties @@ -0,0 +1,5 @@ +build.artifact=jpo-deduplicator +build.group=usdot.jpo.ode +build.name=jpo-deduplicator +build.time=2024-12-13T16\:05\:07.541Z +build.version=1.3.1 diff --git a/sample.env b/sample.env index df92363..c6729cf 100644 --- a/sample.env +++ b/sample.env @@ -2,6 +2,9 @@ # (Required) The IP address of Docker host machine which can be found by running "ifconfig" # Hint: look for "inet addr:" within "eth0" or "en0" for OSX DOCKER_HOST_IP="" +MAVEN_GITHUB_TOKEN= +MAVEN_GITHUB_ORG=usdot-jpo-ode + # Docker compose restart policy: https://docs.docker.com/engine/containers/start-containers-automatically/ RESTART_POLICY="on-failure:3" @@ -24,10 +27,16 @@ RESTART_POLICY="on-failure:3" # - kafka_connect # - kafka_connect # - kafka-connect-setup +# - deduplicator +# - deduplicator # EXAMPLE: COMPOSE_PROFILES=kafka_connect_standalone,kafka_ui,mongo_express COMPOSE_PROFILES=all + + ### COMMON variables - END ### + + ### KAFKA variables - START ### KAFKA_BOOTSTRAP_SERVERS=${DOCKER_HOST_IP}:9092 KAFKA_LOG_RETENTION_HOURS=3 @@ -114,4 +123,4 @@ CONNECT_CREATE_DEDUPLICATOR=true # Create kafka connectors to MongoDB for # NOTE: This script is used to create kafka connectors CONNECT_CONFIG_RELATIVE_PATH="./jikkou/kafka-connectors-values.yaml" -### Kafka connect variables - END ### \ No newline at end of file +### Kafka connect variables - END ### From 0d676c9ad59a259cd3ecfea252fcbc13ae6d6e47 Mon Sep 17 00:00:00 2001 From: John-Wiens Date: Fri, 13 Dec 2024 11:37:03 -0700 Subject: [PATCH 34/44] Dockerfile Build fixes --- .github/workflows/ci.yml | 43 ------------------------------------- jpo-deduplicator/Dockerfile | 2 +- 2 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index d2e8cd0..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: CI -on: - pull_request: - types: [opened, reopened, synchronize] - push: - branches: [develop, master] - -jobs: - docker: - runs-on: ubuntu-latest - steps: - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Build - uses: docker/build-push-action@v3 - with: - build-args: | - MAVEN_GITHUB_TOKEN_NAME=${{ vars.MAVEN_GITHUB_TOKEN_NAME }} - MAVEN_GITHUB_TOKEN=${{ secrets.MAVEN_GITHUB_TOKEN }} - MAVEN_GITHUB_ORG=${{ github.repository_owner }} - secrets: | - MAVEN_GITHUB_TOKEN: ${{ secrets.MAVEN_GITHUB_TOKEN }} - - sonar: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - name: Set up JDK - uses: actions/setup-java@v3 - with: - java-version: "21" - distribution: "temurin" - - name: Run Sonar - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - MAVEN_GITHUB_TOKEN_NAME: ${{ vars.MAVEN_GITHUB_TOKEN_NAME }} - MAVEN_GITHUB_TOKEN: ${{ secrets.MAVEN_GITHUB_TOKEN }} - MAVEN_GITHUB_ORG: ${{ github.repository_owner }} - run: | - cd jpo-deduplicator/jpo-deduplicator - mvn -s settings.xml -e -X clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar -Dsonar.projectKey=usdot-jpo-ode_jpo-deduplicator -Dsonar.projectName=jpo-conflictmonitor -Dsonar.organization=usdot-jpo-ode -Dsonar.host.url=https://sonarcloud.io -Dsonar.branch.name=$GITHUB_REF_NAME diff --git a/jpo-deduplicator/Dockerfile b/jpo-deduplicator/Dockerfile index c3df17e..6228e45 100644 --- a/jpo-deduplicator/Dockerfile +++ b/jpo-deduplicator/Dockerfile @@ -23,7 +23,7 @@ ENV MAVEN_GITHUB_ORG=$MAVEN_GITHUB_ORG # Copy and Build Deduplicator WORKDIR /home COPY ./jpo-deduplicator/pom.xml ./jpo-deduplicator/ -COPY ./settings.xml ./jpo-deduplicator/ +COPY ./jpo-deduplicator/settings.xml ./jpo-deduplicator/ WORKDIR /home/jpo-deduplicator RUN mvn -s settings.xml dependency:resolve From e2dc9f034cf3b0807c843a0c613e145742314997 Mon Sep 17 00:00:00 2001 From: John-Wiens Date: Fri, 13 Dec 2024 12:00:57 -0700 Subject: [PATCH 35/44] Testing Dockerhub build --- .github/workflows/dockerhub.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index b7d3c85..2ee98f8 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -1,11 +1,13 @@ name: "DockerHub Build and Push" on: - push: - branches: - - "develop" - - "master" - - "release/*" + # push: + # branches: + # - "develop" + # - "master" + # - "release/*" + pull_request: + types: [opened, synchronize, reopened] jobs: dockerhub-jpo-deduplicator: runs-on: ubuntu-latest From 5094fc416dd27e2146da5ead3cf8e257299002c1 Mon Sep 17 00:00:00 2001 From: John-Wiens Date: Fri, 13 Dec 2024 12:05:17 -0700 Subject: [PATCH 36/44] updated docker context location --- .github/workflows/dockerhub.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 2ee98f8..5fda4c6 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -19,7 +19,6 @@ jobs: - name: Login to DockerHub uses: docker/login-action@v2 with: - context: jpo-deduplicator username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -30,6 +29,7 @@ jobs: - name: Build uses: docker/build-push-action@v3 with: + context: jpo-deduplicator push: true tags: usdotjpoode/jpo-deduplicator:${{ env.TAG }} build-args: | From e53dc59ce99ca746b1d6d6486c8bebd1ce3808cd Mon Sep 17 00:00:00 2001 From: John-Wiens Date: Fri, 13 Dec 2024 12:18:02 -0700 Subject: [PATCH 37/44] Setting dockerhub build back to release mode --- .github/workflows/dockerhub.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 5fda4c6..00e2704 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -1,13 +1,12 @@ name: "DockerHub Build and Push" on: - # push: - # branches: - # - "develop" - # - "master" - # - "release/*" - pull_request: - types: [opened, synchronize, reopened] + push: + branches: + - "develop" + - "master" + - "release/*" + jobs: dockerhub-jpo-deduplicator: runs-on: ubuntu-latest From f9c9bb6bec090d6652edfb64de986bd870ac92a9 Mon Sep 17 00:00:00 2001 From: John-Wiens Date: Fri, 13 Dec 2024 15:34:44 -0700 Subject: [PATCH 38/44] Added Configurable Deduplication --- docker-compose-deduplicator.yml | 10 ++++++++++ .../src/main/resources/application.yaml | 14 +++++++------- sample.env | 17 +++++++++++++++++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/docker-compose-deduplicator.yml b/docker-compose-deduplicator.yml index 6b3a021..b20c6f4 100644 --- a/docker-compose-deduplicator.yml +++ b/docker-compose-deduplicator.yml @@ -15,6 +15,16 @@ services: DOCKER_HOST_IP: ${DOCKER_HOST_IP} KAFKA_BOOTSTRAP_SERVERS: ${KAFKA_BOOTSTRAP_SERVERS:?error} spring.kafka.bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:?error} + enableProcessedMapDeduplication: ${ENABLE_PROCESSED_MAP_DEDUPLICATION} + enableProcessedMapWktDeduplication: ${ENABLE_PROCESSED_MAP_WKT_DEDUPLICATION} + enableOdeMapDeduplication: ${ENABLE_ODE_MAP_DEDUPLICATION} + enableOdeTimDeduplication: ${ENABLE_ODE_TIM_DEDUPLICATION} + enableOdeRawEncodedTimDeduplication: ${ENABLE_ODE_RAW_ENCODED_TIM_DEDUPLICATION} + enableProcessedSpatDeduplication: ${KAFKA_TOPIC_CREATE_GEOJSONCONVERTER} + enableOdeBsmDeduplication: ${ENABLE_ODE_BSM_DEDUPLICATION} + + + healthcheck: test: ["CMD", "java", "-version"] interval: 10s diff --git a/jpo-deduplicator/jpo-deduplicator/src/main/resources/application.yaml b/jpo-deduplicator/jpo-deduplicator/src/main/resources/application.yaml index 3102c6b..9e6b89d 100644 --- a/jpo-deduplicator/jpo-deduplicator/src/main/resources/application.yaml +++ b/jpo-deduplicator/jpo-deduplicator/src/main/resources/application.yaml @@ -17,32 +17,32 @@ log4j.logger.org.apache.kafka: OFF # Processed Map Configuration kafkaTopicProcessedMap: topic.ProcessedMap kafkaTopicDeduplicatedProcessedMap: topic.DeduplicatedProcessedMap -enableProcessedMapDeduplication: true +enableProcessedMapDeduplication: false # Processed Map WKT Configuration kafkaTopicProcessedMapWKT: topic.ProcessedMapWKT kafkaTopicDeduplicatedProcessedMapWKT: topic.DeduplicatedProcessedMapWKT -enableProcessedMapWktDeduplication: true +enableProcessedMapWktDeduplication: false # Ode Map Json Configuration kafkaTopicOdeMapJson: topic.OdeMapJson kafkaTopicDeduplicatedOdeMapJson: topic.DeduplicatedOdeMapJson -enableOdeMapDeduplication: true +enableOdeMapDeduplication: false # Ode Tim Json Configuration kafkaTopicOdeTimJson: topic.OdeTimJson kafkaTopicDeduplicatedOdeTimJson: topic.DeduplicatedOdeTimJson -enableOdeTimDeduplication: true +enableOdeTimDeduplication: false # Ode Raw Encoded Tim Json Configuration kafkaTopicOdeRawEncodedTimJson: topic.OdeRawEncodedTIMJson kafkaTopicDeduplicatedOdeRawEncodedTimJson: topic.DeduplicatedOdeRawEncodedTIMJson -enableOdeRawEncodedTimDeduplication: true +enableOdeRawEncodedTimDeduplication: false # Ode Bsm Json Configuration kafkaTopicOdeBsmJson: topic.OdeBsmJson kafkaTopicDeduplicatedOdeBsmJson: topic.DeduplicatedOdeBsmJson -enableOdeBsmDeduplication: true +enableOdeBsmDeduplication: false odeBsmMaximumTimeDelta: 10000 # Milliseconds odeBsmMaximumPositionDelta: 1 # Meter odeBsmAlwaysIncludeAtSpeed: 1 # Meter / Second @@ -50,7 +50,7 @@ odeBsmAlwaysIncludeAtSpeed: 1 # Meter / Second # Processed Map Configuration kafkaTopicProcessedSpat: topic.ProcessedSpat kafkaTopicDeduplicatedProcessedSpat: topic.DeduplicatedProcessedSpat -enableProcessedSpatDeduplication: true +enableProcessedSpatDeduplication: false # Amount of time to wait to try and increase batching diff --git a/sample.env b/sample.env index c6729cf..dbb41ca 100644 --- a/sample.env +++ b/sample.env @@ -124,3 +124,20 @@ CONNECT_CREATE_DEDUPLICATOR=true # Create kafka connectors to MongoDB for CONNECT_CONFIG_RELATIVE_PATH="./jikkou/kafka-connectors-values.yaml" ### Kafka connect variables - END ### + +### DEDUPLICATOR variables - START ### + +ENABLE_PROCESSED_MAP_DEDUPLICATION=${KAFKA_TOPIC_CREATE_GEOJSONCONVERTER} +ENABLE_PROCESSED_MAP_WKT_DEDUPLICATION=${KAFKA_TOPIC_CREATE_GEOJSONCONVERTER} +ENABLE_ODE_MAP_DEDUPLICATION=${KAFKA_TOPIC_CREATE_ODE} +ENABLE_ODE_TIM_DEDUPLICATION=${KAFKA_TOPIC_CREATE_ODE} +ENABLE_ODE_RAW_ENCODED_TIM_DEDUPLICATION=${KAFKA_TOPIC_CREATE_ODE} +ENABLE_PROCESSED_SPAT_DEDUPLICATION=${KAFKA_TOPIC_CREATE_GEOJSONCONVERTER} +ENABLE_ODE_BSM_DEDUPLICATION=${KAFKA_TOPIC_CREATE_ODE} + + + +### DEDUPLICATOR variables - END ### + + + From d66d7e823bd78b0fb30db3bfdd9b6596a3ccb108 Mon Sep 17 00:00:00 2001 From: john-wiens Date: Mon, 16 Dec 2024 10:13:47 -0700 Subject: [PATCH 39/44] Updated readme entry for jpo-deduplicator --- README.md | 70 +++++++++++++++++++++++++++++++++ docker-compose-deduplicator.yml | 2 +- sample.env | 6 ++- 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cee506e..ea2e9d4 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,10 @@ The JPO ITS utilities repository serves as a central location for deploying open - [Configuration](#configuration) - [Configure Kafka Connector Creation](#configure-kafka-connector-creation) - [Quick Run](#quick-run-2) + - [5. Deduplicator](#5-jpo-Deduplicator) + - [Deduplication Configuration](#deduplication-config) + - [Github Token Generation](#generate-a-github-token) + - [Quick Run](#quick-run-3) @@ -185,4 +189,70 @@ The following environment variables can be used to configure Kafka Connectors: 3. Click `OdeBsmJson`, and now you should see your message! 8. Feel free to test this with other topics or by producing to these topics using the [ODE](https://github.com/usdot-jpo-ode/jpo-ode) + + + +## 5. jpo-deduplicator +The JPO-Deduplicator is a Kafka Java spring-boot application designed to reduce the number of messages stored and processed in the ODE system. This is done by reading in messages from an input topic (such as topic.ProcessedMap) and outputting a subset of those messages on a related output topic (topic.DeduplicatedProcessedMap). Functionally, this is done by removing deduplicate messages from the input topic and only passing on unique messages. In addition, each topic will pass on at least 1 message per hour even if the message is a duplicate. This behavior helps ensure messages are still flowing through the system. The following topics currently support deduplication. + +- topic.ProcessedMap -> topic.DeduplicatedProcessedMap +- topic.ProcessedMapWKT -> topic.DeduplicatedProcessedMapWKT +- topic.OdeMapJson -> topic.DeduplicatedOdeMapJson +- topic.OdeTimJson -> topic.DeduplicatedOdeTimJson +- topic.OdeRawEncodedTIMJson -> topic.DeduplicatedOdeRawEncodedTIMJson +- topic.OdeBsmJson -> topic.DeduplicatedOdeBsmJson +- topic.ProcessedSpat -> topic.DeduplicatedProcessedSpat + +### Deduplication Config + +When running the jpo-deduplication as a submodule in jpo-utils, the deduplicator will automatically turn on deduplication for a topic when that topic is created. For example if the KAFKA_TOPIC_CREATE_GEOJSONCONVERTER environment variable is set to true, the deduplicator will start performing deduplication for ProcessedMap, ProcessedMapWKT, and ProcessedSpat data. + +To manually configure deduplication for a topic, the following environment variables can also be used. + +| Environment Variable | Description | +|---|---| +| `ENABLE_PROCESSED_MAP_DEDUPLICATION` | `true` / `false` - Enable ProcessedMap message Deduplication | +| `ENABLE_PROCESSED_MAP_WKT_DEDUPLICATION` | `true` / `false` - Enable ProcessedMap WKT message Deduplication | +| `ENABLE_ODE_MAP_DEDUPLICATION` | `true` / `false` - Enable ODE MAP message Deduplication | +| `ENABLE_ODE_TIM_DEDUPLICATION` | `true` / `false` - Enable ODE TIM message Deduplication | +| `ENABLE_ODE_RAW_ENCODED_TIM_DEDUPLICATION` | `true` / `false` - Enable ODE Raw Encoded TIM Deduplication | +| `ENABLE_PROCESSED_SPAT_DEDUPLICATION` | `true` / `false` - Enable ProcessedSpat Deduplication | +| `ENABLE_ODE_BSM_DEDUPLICATION` | `true` / `false` - Enable ODE BSM Deduplication | + +### Generate a Github Token + +A GitHub token is required to pull artifacts from GitHub repositories. This is required to obtain the jpo-deduplicator jars and must be done before attempting to build this repository. + +1. Log into GitHub. +2. Navigate to Settings -> Developer settings -> Personal access tokens. +3. Click "New personal access token (classic)". + 1. As of now, GitHub does not support `Fine-grained tokens` for obtaining packages. +4. Provide a name and expiration for the token. +5. Select the `read:packages` scope. +6. Click "Generate token" and copy the token. +7. Copy the token name and token value into your `.env` file. + +For local development the following steps are also required +8. Create a copy of [settings.xml](jpo-deduplicator/jpo-deduplicator/settings.xml) and save it to `~/.m2/settings.xml` +9. Update the variables in your `~/.m2/settings.xml` with the token value and target jpo-ode organization. + +### Quick Run +1. Create a copy of `sample.env` and rename it to `.env`. +2. Update the variable `MAVEN_GITHUB_TOKEN` to a github token used for downloading jar file dependencies. For full instructions on how to generate a token please see here: +3. Set the password for `MONGO_ADMIN_DB_PASS` and `MONGO_READ_WRITE_PASS` environmental variables to a secure password. +4. Set the `COMPOSE_PROFILES` variable to: `kafka,kafka_ui,kafka_setup, jpo-deduplicator` +5. Navigate back to the root directory and run the following command: `docker compose up -d` +6. Produce a sample message to one of the sink topics by using `kafka_ui` by: + 1. Go to `localhost:8001` + 2. Click local -> Topics + 3. Select `topic.OdeMapJson` + 4. Select `Produce Message` + 5. Copy in sample JSON for a Map Message + 6. Click `Produce Message` multiple times +7. View the synced message in `kafka_ui` by: + 1. Go to `localhost:8001` + 2. Click local -> Topics + 3. Select `topic.DeduplicatedOdeMapJson` + 4. You should now see only one copy of the map message sent. + [Back to top](#toc) diff --git a/docker-compose-deduplicator.yml b/docker-compose-deduplicator.yml index b20c6f4..5c315d7 100644 --- a/docker-compose-deduplicator.yml +++ b/docker-compose-deduplicator.yml @@ -20,7 +20,7 @@ services: enableOdeMapDeduplication: ${ENABLE_ODE_MAP_DEDUPLICATION} enableOdeTimDeduplication: ${ENABLE_ODE_TIM_DEDUPLICATION} enableOdeRawEncodedTimDeduplication: ${ENABLE_ODE_RAW_ENCODED_TIM_DEDUPLICATION} - enableProcessedSpatDeduplication: ${KAFKA_TOPIC_CREATE_GEOJSONCONVERTER} + enableProcessedSpatDeduplication: ${ENABLE_PROCESSED_SPAT_DEDUPLICATION} enableOdeBsmDeduplication: ${ENABLE_ODE_BSM_DEDUPLICATION} diff --git a/sample.env b/sample.env index dbb41ca..797d2aa 100644 --- a/sample.env +++ b/sample.env @@ -2,8 +2,6 @@ # (Required) The IP address of Docker host machine which can be found by running "ifconfig" # Hint: look for "inet addr:" within "eth0" or "en0" for OSX DOCKER_HOST_IP="" -MAVEN_GITHUB_TOKEN= -MAVEN_GITHUB_ORG=usdot-jpo-ode # Docker compose restart policy: https://docs.docker.com/engine/containers/start-containers-automatically/ @@ -127,6 +125,10 @@ CONNECT_CONFIG_RELATIVE_PATH="./jikkou/kafka-connectors-values.yaml" ### DEDUPLICATOR variables - START ### +# Required for building the deduplicator. Documentation: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-apache-maven-registry#authenticating-to-github-packages +MAVEN_GITHUB_TOKEN= +MAVEN_GITHUB_ORG=usdot-jpo-ode + ENABLE_PROCESSED_MAP_DEDUPLICATION=${KAFKA_TOPIC_CREATE_GEOJSONCONVERTER} ENABLE_PROCESSED_MAP_WKT_DEDUPLICATION=${KAFKA_TOPIC_CREATE_GEOJSONCONVERTER} ENABLE_ODE_MAP_DEDUPLICATION=${KAFKA_TOPIC_CREATE_ODE} From b3b9a9b97ac7f36cfd933d00d27dad8c40d0e2e8 Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:23:33 -0700 Subject: [PATCH 40/44] fixing the collectionName for topic.OdeRawEncodedTIMJson --- jikkou/kafka-connectors-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jikkou/kafka-connectors-values.yaml b/jikkou/kafka-connectors-values.yaml index f95acba..4e979ac 100644 --- a/jikkou/kafka-connectors-values.yaml +++ b/jikkou/kafka-connectors-values.yaml @@ -134,7 +134,7 @@ apps: generateTimestamp: true connectorName: DeduplicatedOdeTimJson - topicName: topic.OdeRawEncodedTIMJson - collectionName: OdeTimJson + collectionName: OdeRawEncodedTIMJson generateTimestamp: true connectorName: DeduplicatedOdeRawEncodedTIMJson - topicName: topic.DeduplicatedOdeBsmJson From 3108295b48702b31f3769ba0a54a4f8c290b6a3c Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:35:27 -0700 Subject: [PATCH 41/44] fix source topic for topic.DeduplicatedOdeRawEncodedTIMJson --- jikkou/kafka-connectors-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jikkou/kafka-connectors-values.yaml b/jikkou/kafka-connectors-values.yaml index 4e979ac..cad383a 100644 --- a/jikkou/kafka-connectors-values.yaml +++ b/jikkou/kafka-connectors-values.yaml @@ -133,7 +133,7 @@ apps: collectionName: OdeTimJson generateTimestamp: true connectorName: DeduplicatedOdeTimJson - - topicName: topic.OdeRawEncodedTIMJson + - topicName: topic.DeduplicatedOdeRawEncodedTIMJson collectionName: OdeRawEncodedTIMJson generateTimestamp: true connectorName: DeduplicatedOdeRawEncodedTIMJson From 71244edb40991fe5ef6117d0d4da39cf3f09b095 Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:53:40 -0700 Subject: [PATCH 42/44] updates to .gitignore to ignore auto IDE builds of the deduplicator from polluting git --- .gitignore | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 845959d..e417394 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,163 @@ -**/.env \ No newline at end of file +**/.env + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file From d8f1b30c4293fc3e6b1ad3f5405b72a9fc3144f8 Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:56:39 -0700 Subject: [PATCH 43/44] updates to just ignore the build folder --- .gitignore | 162 +---------------------------------------------------- 1 file changed, 1 insertion(+), 161 deletions(-) diff --git a/.gitignore b/.gitignore index e417394..8d161e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,163 +1,3 @@ **/.env -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/latest/usage/project/#working-with-version-control -.pdm.toml -.pdm-python -.pdm-build/ - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +**/target \ No newline at end of file From 4d76447036d5fd9c17140fdb2ee09026fbee314e Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:05:28 -0700 Subject: [PATCH 44/44] Delete build-info.properties --- .../target/classes/META-INF/build-info.properties | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 jpo-deduplicator/jpo-deduplicator/target/classes/META-INF/build-info.properties diff --git a/jpo-deduplicator/jpo-deduplicator/target/classes/META-INF/build-info.properties b/jpo-deduplicator/jpo-deduplicator/target/classes/META-INF/build-info.properties deleted file mode 100644 index b9956a7..0000000 --- a/jpo-deduplicator/jpo-deduplicator/target/classes/META-INF/build-info.properties +++ /dev/null @@ -1,5 +0,0 @@ -build.artifact=jpo-deduplicator -build.group=usdot.jpo.ode -build.name=jpo-deduplicator -build.time=2024-12-13T16\:05\:07.541Z -build.version=1.3.1