Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Detour Road Events and upgrade library to NET 9 #18

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# WZDx .NET Library

This repository contains the source code for IBI Group's [.NET 6.0](https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-6) [WZDx (Work Zone Data Exchange)](https://github.com/usdot-jpo-ode/wzdx) class library, `IBI.WZDx`.
This repository contains the source code for IBI Group's [.NET 9.0](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-9/overview) [WZDx (Work Zone Data Exchange)](https://github.com/usdot-jpo-ode/wzdx) class library, `IBI.WZDx`.

## About

The `IBI.WZDx` class library provides models and utitlies for producing and consuming [Work Zone Data Exchange (WZDx)](https://github.com/usdot-jpo-ode/wzdx) data feeds.
The `IBI.WZDx` class library provides models and utilities for producing and consuming [Work Zone Data Exchange (WZDx)](https://github.com/usdot-jpo-ode/wzdx) data feeds.

The library provides the following functionality:

Expand All @@ -16,8 +16,6 @@ The library provides the following functionality:

WZDx versions 4.0, 4.1, and 4.2 are supported; the [WzdxSerializer](./src/IBI.WZDx/Serialization/WzdxSerializer.cs) defaults to outputting v4.2 (latest WZDx).

[Detour road events](https://github.com/usdot-jpo-ode/wzdx/blob/main/spec-content/objects/DetourRoadEvent.md) are not supported. When provided with a Work Zone Feed that includes detour road events, the WzdxSerializer.DeserializeFeed method will deserialize the detour events into a [RoadEventFeature](./src/IBI.WZDx/Models/RoadEvents/RoadEventFeature.cs) with `Properties` as `null`.

## Usage

### NuGet Package
Expand Down Expand Up @@ -71,7 +69,7 @@ The PATCH version number is incremented as changes are made to the library that

## Tests

This solution includes a [IBI.WZDx.UnitTests](/tests/IBI.WZDx.UnitTests/) Xunit test project. Run all unit tests with the following command:
This solution includes a [IBI.WZDx.UnitTests](tests/IBI.WZDx.UnitTests) Xunit test project. Run all unit tests with the following command:

```
dotnet test
Expand Down
29 changes: 29 additions & 0 deletions src/IBI.WZDx/Equality/DoubleExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;

namespace IBI.WZDx.Equality;

/// <summary>
/// Extensions for <see cref="double"/> related to equality.
/// </summary>
public static class DoubleExtensions
{
/// <summary>
/// Determines whether two nullable double values are approximately equal.
/// </summary>
/// <param name="value">The first nullable double value to compare.</param>
/// <param name="other">The second nullable double value to compare.</param>
/// <returns>
/// True if both values are null, or their absolute difference is less than or equal
/// to <see cref="double.Epsilon"/>; otherwise, false.
/// </returns>
public static bool NullEqualsApproximation(this double? value, double? other)
{
if (value is null && other is null)
return true;

if (value is null || other is null)
return false;

return Math.Abs(value.Value - other.Value) <= double.Epsilon;
}
}
8 changes: 4 additions & 4 deletions src/IBI.WZDx/IBI.WZDx.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<PackageId>IBI.WZDx</PackageId>
<Description>Models and utitlies for producing and consuming Work Zone Data Exchange (WZDx) data feeds.</Description>
<PackageTags>WZDx;Work Zone Data Exchange;Work Zone Feed;Road Event;Device Feed;Field Device;GeoJSON</PackageTags>
<Authors>IBI Group</Authors>
<VersionPrefix>4.2.0</VersionPrefix>
<VersionPrefix>4.2.1</VersionPrefix>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryType>git</RepositoryType>
Expand All @@ -21,8 +21,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Macross.Json.Extensions" Version="2.2.0" />
<PackageReference Include="System.Text.Json" Version="6.0.2" />
<PackageReference Include="Macross.Json.Extensions" Version="3.0.0" />
<PackageReference Include="System.Text.Json" Version="9.0.2" />
</ItemGroup>

</Project>
8 changes: 7 additions & 1 deletion src/IBI.WZDx/Models/RoadEvents/RoadEventCoreDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ public record RoadEventCoreDetails(
string? Description = null,
DateTimeOffset? CreationDate = null,
DateTimeOffset? UpdateDate = null,
#pragma warning disable CS0618 // Type or member is obsolete
[property: Obsolete("Use RelatedRoadEvents instead.")]Relationship? Relationship = null
#pragma warning restore CS0618 // Type or member is obsolete
)
{
/// <summary>
/// Determine if an other <see cref="RoadEventCoreDetails"/> is equal to this <see cref="RoadEventCoreDetails"/>.
/// Determine if another <see cref="RoadEventCoreDetails"/> is equal to this <see cref="RoadEventCoreDetails"/>.
/// </summary>
public virtual bool Equals(RoadEventCoreDetails? other)
{
Expand All @@ -75,7 +77,9 @@ public virtual bool Equals(RoadEventCoreDetails? other)
&& Description == other.Description
&& CreationDate == other.CreationDate
&& UpdateDate == other.UpdateDate
#pragma warning disable CS0618 // Type or member is obsolete
&& Relationship == other.Relationship;
#pragma warning restore CS0618 // Type or member is obsolete
}

/// <inheritdoc/>
Expand Down Expand Up @@ -105,7 +109,9 @@ public override int GetHashCode()
hash.Add(Description);
hash.Add(CreationDate);
hash.Add(UpdateDate);
#pragma warning disable CS0618 // Type or member is obsolete
hash.Add(Relationship);
#pragma warning restore CS0618 // Type or member is obsolete

return hash.ToHashCode();
}
Expand Down
109 changes: 109 additions & 0 deletions src/IBI.WZDx/Models/RoadEvents/WorkZones/DetourRoadEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using IBI.WZDx.Equality;

namespace IBI.WZDx.Models.RoadEvents.WorkZones;

/// <summary>
/// Describes a detour on a roadway. It can be either a segment of a detour
/// (each segment represented by its own <see cref="DetourRoadEvent"/>) or the entire detour.
/// </summary>
/// <param name="CoreDetails">
/// The core details of the road event that apply to all types of road events, not specific to detour road events.
/// </param>
/// <param name="StartDate">
/// The UTC time and date when the event begins.
/// </param>
/// <param name="EndDate">
/// The UTC time and date when the event ends.
/// </param>
/// <param name="IsStartDateVerified">
/// Indicates if work has been confirmed to have started, such as from a person or field device.
/// </param>
/// <param name="IsEndDateVerified">
/// Indicates if work has been confirmed to have ended, such as from a person or field device.
/// </param>
/// <param name="BeginningCrossStreet">
/// Name or number of the nearest cross street along the roadway where the event begins.
/// </param>
/// <param name="EndingCrossStreet">
/// Name or number of the nearest cross street along the roadway where the event ends.
/// </param>
/// <param name="BeginningMilepost">
/// The linear distance measured against a milepost marker along a roadway where the event begins.
/// </param>
/// <param name="EndingMilepost">
/// The linear distance measured against a milepost marker along a roadway where the event ends.
/// </param>
/// <param name="EventStatus">
/// The status of the event.
/// </param>
/// <param name="StartDateAccuracy">
/// A measure of how accurate the start date-time is.
/// </param>
/// <param name="EndDateAccuracy">
/// A measure of how accurate the end date-time is.
/// </param>
public record DetourRoadEvent(
RoadEventCoreDetails CoreDetails,
DateTimeOffset StartDate,
DateTimeOffset EndDate,
bool? IsStartDateVerified = null,
bool? IsEndDateVerified = null,
string? BeginningCrossStreet = null,
string? EndingCrossStreet = null,
double? BeginningMilepost = null,
double? EndingMilepost = null,
#pragma warning disable CS0618 // Type or member is obsolete
[property: Obsolete("Determine an event's status based on the dates and verification properties.")]
EventStatus? EventStatus = null,
[property: Obsolete("Use IsStartDateVerified instead.")]TimeVerification? StartDateAccuracy = null,
[property: Obsolete("Use IsEndDateVerified instead.")]TimeVerification? EndDateAccuracy = null
#pragma warning restore CS0618 // Type or member is obsolete
) : IRoadEvent
{
/// <summary>
/// Determine if another <see cref="DetourRoadEvent"/> is equal to this <see cref="DetourRoadEvent"/>.
/// </summary>
public virtual bool Equals(DetourRoadEvent? other)
{
return other != null
&& CoreDetails == other.CoreDetails
&& StartDate == other.StartDate
&& EndDate == other.EndDate
&& IsStartDateVerified == other.IsStartDateVerified
&& IsEndDateVerified == other.IsEndDateVerified
&& BeginningCrossStreet == other.BeginningCrossStreet
&& EndingCrossStreet == other.EndingCrossStreet
&& BeginningMilepost.NullEqualsApproximation(other.BeginningMilepost)
&& EndingMilepost.NullEqualsApproximation(other.EndingMilepost)
#pragma warning disable CS0618 // Type or member is obsolete
&& EventStatus == other.EventStatus
&& StartDateAccuracy == other.StartDateAccuracy
&& EndDateAccuracy == other.EndDateAccuracy;
#pragma warning restore CS0618 // Type or member is obsolete
}

/// <inheritdoc/>
public override int GetHashCode()
{
var hash = new HashCode();

hash.Add(CoreDetails);
hash.Add(StartDate);
hash.Add(EndDate);
hash.Add(IsStartDateVerified);
hash.Add(IsEndDateVerified);
hash.Add(BeginningCrossStreet);
hash.Add(EndingCrossStreet);
hash.Add(BeginningMilepost);
hash.Add(EndingMilepost);
#pragma warning disable CS0618 // Type or member is obsolete
hash.Add(EventStatus);
hash.Add(StartDateAccuracy);
hash.Add(EndDateAccuracy);
#pragma warning restore CS0618 // Type or member is obsolete

return hash.ToHashCode();
}
}
18 changes: 17 additions & 1 deletion src/IBI.WZDx/Models/RoadEvents/WorkZones/WorkZoneRoadEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ namespace IBI.WZDx.Models.RoadEvents.WorkZones;
/// The status of the event.
/// </param>
/// <param name="TypesOfWork">
/// A list of the types of work being done in a road event and an indiciation of if each type
/// A list of the types of work being done in a road event and an indication of if each type
/// results in an architectural change to the roadway.
/// </param>
/// <param name="WorkerPresence">
Expand Down Expand Up @@ -108,8 +108,10 @@ public record WorkZoneRoadEvent(
VehicleImpact VehicleImpact,
bool? IsStartDateVerified = null,
bool? IsEndDateVerified = null,
#pragma warning disable CS0618 // Type or member is obsolete
[property: Obsolete("Use IsStartDateVerified instead.")]TimeVerification? StartDateAccuracy = null,
[property: Obsolete("Use IsEndDateVerified instead.")]TimeVerification? EndDateAccuracy = null,
#pragma warning restore CS0618 // Type or member is obsolete
bool? IsStartPositionVerified = null,
bool? IsEndPositionVerified = null,
[property: Obsolete("Use IsStartPositionVerified instead.")]SpatialVerification? BeginningAccuracy = null,
Expand All @@ -119,8 +121,10 @@ public record WorkZoneRoadEvent(
string? EndingCrossStreet = null,
double? BeginningMilepost = null,
double? EndingMilepost = null,
#pragma warning disable CS0618 // Type or member is obsolete
[property: Obsolete("Determine an event's status based on the dates and verification properties.")]
EventStatus? EventStatus = null,
#pragma warning restore CS0618 // Type or member is obsolete
IEnumerable<TypeOfWork>? TypesOfWork = null,
WorkerPresence? WorkerPresence = null,
double? ReducedSpeedLimitKph = null,
Expand All @@ -142,18 +146,24 @@ public virtual bool Equals(WorkZoneRoadEvent? other)
&& VehicleImpact == other.VehicleImpact
&& IsStartDateVerified == other.IsStartDateVerified
&& IsEndDateVerified == other.IsEndDateVerified
#pragma warning disable CS0618 // Type or member is obsolete
&& StartDateAccuracy == other.StartDateAccuracy
&& EndDateAccuracy == other.EndDateAccuracy
#pragma warning restore CS0618 // Type or member is obsolete
&& IsStartPositionVerified == other.IsStartPositionVerified
&& IsEndPositionVerified == other.IsEndPositionVerified
#pragma warning disable CS0618 // Type or member is obsolete
&& BeginningAccuracy == other.BeginningAccuracy
&& EndingAccuracy == other.EndingAccuracy
#pragma warning restore CS0618 // Type or member is obsolete
&& Lanes.NullHandlingSequenceEqual(other.Lanes)
&& BeginningCrossStreet == other.BeginningCrossStreet
&& EndingCrossStreet == other.EndingCrossStreet
&& BeginningMilepost == other.BeginningMilepost
&& EndingMilepost == other.EndingMilepost
#pragma warning disable CS0618 // Type or member is obsolete
&& EventStatus == other.EventStatus
#pragma warning restore CS0618 // Type or member is obsolete
&& TypesOfWork.NullHandlingSequenceEqual(other.TypesOfWork)
&& WorkerPresence == other.WorkerPresence
&& ReducedSpeedLimitKph == other.ReducedSpeedLimitKph
Expand All @@ -174,12 +184,16 @@ public override int GetHashCode()
hash.Add(VehicleImpact);
hash.Add(IsStartDateVerified);
hash.Add(IsEndDateVerified);
#pragma warning disable CS0618 // Type or member is obsolete
hash.Add(StartDateAccuracy);
hash.Add(EndDateAccuracy);
#pragma warning restore CS0618 // Type or member is obsolete
hash.Add(IsStartPositionVerified);
hash.Add(IsEndPositionVerified);
#pragma warning disable CS0618 // Type or member is obsolete
hash.Add(BeginningAccuracy);
hash.Add(EndingAccuracy);
#pragma warning restore CS0618 // Type or member is obsolete

if (Lanes != null)
{
Expand All @@ -193,7 +207,9 @@ public override int GetHashCode()
hash.Add(EndingCrossStreet);
hash.Add(BeginningMilepost);
hash.Add(EndingMilepost);
#pragma warning disable CS0618 // Type or member is obsolete
hash.Add(EventStatus);
#pragma warning restore CS0618 // Type or member is obsolete

if (TypesOfWork != null)
{
Expand Down
24 changes: 10 additions & 14 deletions src/IBI.WZDx/Serialization/RoadEventConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,18 @@ internal class RoadEventConverter : JsonConverter<IRoadEvent>
/// </returns>
public override IRoadEvent? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using var jsonDocument = JsonDocument.ParseValue(ref reader);
var jsonObject = jsonDocument.RootElement.GetRawText();
var eventType = jsonDocument.RootElement.GetProperty("core_details")
using JsonDocument jsonDocument = JsonDocument.ParseValue(ref reader);
string jsonObject = jsonDocument.RootElement.GetRawText();
string? eventType = jsonDocument.RootElement.GetProperty("core_details")
.GetProperty("event_type")
.GetString();

Func<Type, IRoadEvent?> deserializeAsType = type =>
JsonSerializer.Deserialize(jsonObject, type, options) as IRoadEvent;

IRoadEvent? DeserializeAsType(Type type) => JsonSerializer.Deserialize(jsonObject, type, options) as IRoadEvent;

return eventType switch
{
"work-zone" => deserializeAsType(typeof(WorkZoneRoadEvent)),
"detour" => null,
"work-zone" => DeserializeAsType(typeof(WorkZoneRoadEvent)),
"detour" => DeserializeAsType(typeof(DetourRoadEvent)),
_ => throw new JsonException($"Unsupported event type '{eventType}'.")
};
}
Expand All @@ -60,12 +59,9 @@ internal class RoadEventConverter : JsonConverter<IRoadEvent>
/// <param name="options">An object that specifies serialization options to use.</param>
/// <exception cref="ArgumentNullException"><paramref name="value"/> cannot be null.</exception>
public override void Write(Utf8JsonWriter writer, IRoadEvent value, JsonSerializerOptions options)
{
if (value == null)
throw new ArgumentNullException("value");

var type = value.GetType();

{
ArgumentNullException.ThrowIfNull(value);
Type type = value.GetType();
JsonSerializer.Serialize(writer, value, type, options);
}
}
Loading
Loading