Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"recommendations": [
"csharpier.csharpier-vscode"
]
}
19 changes: 18 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"Geospatial",
"HWYNAM",
"MVVM",
"ONEWAY",
"pathspec",
"POSTDIR",
"POSTTYPE",
Expand All @@ -36,5 +37,21 @@
],
"python.testing.pytestArgs": ["cli"],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
"python.testing.pytestEnabled": true,
"chat.tools.terminal.autoApprove": {
"/^dotnet build add-in\\\\UGRC\\.UtransTools\\\\UGRC\\.UtransTools\\.csproj --no-restore$/": {
"approve": true,
"matchCommandLine": true
}
},
"[csharp]": {
"editor.defaultFormatter": "csharpier.csharpier-vscode"
},
"[xml]": {
"editor.defaultFormatter": "csharpier.csharpier-vscode"
},
"[xaml]": {
"editor.formatOnSave": false
},
"editor.formatOnSave": true
}
32 changes: 29 additions & 3 deletions add-in/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
# UTRANS Tools ArcGIS Pro Add-In
# UTrans Tools ArcGIS Pro Add-In

## Usage

### Adding New Features

Use **Add New** to copy an unlinked new record or records from `DFC_RESULT` into the UTrans roads layer.

1. In the map, select one or more unlinked records in the `DFC_RESULT` layer. A record is eligible when its change type is `N` and its `BASE_FID` is `-1`.
2. Review or update the road attributes shown in the editor. When multiple records are selected, the values are used for each new road.
3. Click **Add New**.

When multiple features are selected, the form values override the county-road values for `CARTOCODE`, `ONEWAY`, `VERT_LEVEL`, `SPEED_LMT`, and `STATUS`. Review these values before clicking **Add New** because the same editor values are applied to every new road.

The add-in creates a UTrans road using each selected county-road geometry and attributes, links the `DFC_RESULT` record to the new road, and marks the DFC disposition as `COMPLETED`. The map selections are cleared after the operation finishes.

## Developer Set Up

Expand Down Expand Up @@ -29,11 +43,23 @@ This project uses **[CSharpier](https://csharpier.com/)** for opinionated, autom
- **Format all files manually:**

```bash
dotnet csharpier .
dotnet csharpier format .
```

- **Verify formatting (used in CI):**

```bash
dotnet csharpier --check .
dotnet csharpier check .
```

### Release Package

Build the add-in in Release mode from the `add-in` directory:

```powershell
dotnet build .\UGRC.UtransTools\UGRC.UtransTools.csproj --configuration Release
```

Or use Visual Studio to build the project in Release mode.

The build copies `UGRC.UtransTools.esriAddinX` to `L:\agrc\utrans\UtransEditing\Pro Add-ins`.
30 changes: 30 additions & 0 deletions add-in/UGRC.UtransTools/Build/BumpAddInVersion.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
param(
[Parameter(Mandatory = $true)]
[string]$ConfigPath
)

$content = [System.IO.File]::ReadAllText($ConfigPath)
$pattern = '(<AddInInfo\b[^>]*\bversion\s*=\s*")(\d+)\.(\d+)(")'
$match = [System.Text.RegularExpressions.Regex]::Match($content, $pattern)

if (-not $match.Success) {
throw "Could not find an AddInInfo version in '$ConfigPath'. Expected a major.minor version."
}

$nextVersion = '{0}.{1}' -f $match.Groups[2].Value, ([int]$match.Groups[3].Value + 1)
$replaceVersion = [System.Text.RegularExpressions.MatchEvaluator]{
param($versionMatch)
$versionMatch.Groups[1].Value + $nextVersion + $versionMatch.Groups[4].Value
}
$updatedContent = [System.Text.RegularExpressions.Regex]::Replace(
$content,
$pattern,
$replaceVersion,
1)

[System.IO.File]::WriteAllText(
$ConfigPath,
$updatedContent,
[System.Text.UTF8Encoding]::new($false))

Write-Host "Updated AddInInfo version to $nextVersion."
4 changes: 2 additions & 2 deletions add-in/UGRC.UtransTools/Config.daml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8" ?>
<ArcGIS defaultAssembly="UGRC.UtransTools.dll"
defaultNamespace="UGRC.UtransTools"
xmlns="http://schemas.esri.com/DADF/Registry"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://schemas.esri.com/DADF/Registry file:///C:/Program%20Files/ArcGIS/Pro/bin/ArcGIS.Desktop.Framework.xsd">

<AddInInfo id="{f893be2f-ad95-46be-9d60-fd6dba457152}" version="1.0" desktopVersion="3.6.59527" product="ArcGISPro">
<AddInInfo id="{f893be2f-ad95-46be-9d60-fd6dba457152}" version="1.14" desktopVersion="3.6.59527" product="ArcGISPro">
<Name>UGRC_UtransTools</Name>
<Description>UGRC_UtransTools description</Description>
<Image>Images\AddinDesktop32.png</Image>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ internal static class UtransEditorConfiguration
"ONEWAY",
"VERT_LEVEL",
"SPEED_LMT",
"STATUS",
};

internal static readonly IReadOnlyDictionary<string, string> CountyOwnershipNames =
Expand Down
3 changes: 2 additions & 1 deletion add-in/UGRC.UtransTools/Models/AttributeReviewField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ bool isAddressRange
public string OriginalUtransValue { get; }
public bool IsAddressRange { get; }
public bool IsDifferent =>
!string.Equals(CountyValue, OriginalUtransValue, System.StringComparison.OrdinalIgnoreCase);
!string.Equals(CountyValue, UtransValue, System.StringComparison.OrdinalIgnoreCase);
public bool IsEdited =>
!string.Equals(UtransValue, OriginalUtransValue, System.StringComparison.Ordinal);
public bool IsUsingCountyValue => _isUsingCountyValue;
Expand All @@ -50,6 +50,7 @@ public string UtransValue

_utransValue = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsDifferent));
OnPropertyChanged(nameof(IsEdited));
}
}
Expand Down
46 changes: 39 additions & 7 deletions add-in/UGRC.UtransTools/Models/EditorReviewState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal sealed class EditorReviewState : INotifyPropertyChanged
private string _oneway = string.Empty;
private string _verticalLevel = string.Empty;
private string _speedLimit = string.Empty;
private string _status = string.Empty;

internal EditorReviewState()
{
Expand All @@ -30,6 +31,16 @@ internal EditorReviewState()
)
)
);
Cartocode = "11";
Oneway = "0";
VerticalLevel = "0";
SpeedLimit = "25";
Status = "Active";
DfcStatus = "COMPLETED";
_initialCartocode = Cartocode;
_initialOneway = Oneway;
_initialVerticalLevel = VerticalLevel;
_initialSpeedLimit = SpeedLimit;

static AttributeReviewField CreateEmptyField(string fieldName, bool isAddressRange) =>
new(fieldName, string.Empty, string.Empty, isAddressRange);
Expand All @@ -39,6 +50,7 @@ internal EditorReviewState(DfcSelectionSnapshot selection)
{
Selection = selection;
var utransRoad = selection.UtransRoad;
var roadValues = utransRoad ?? selection.CountyRoad;
Fields = new ObservableCollection<AttributeReviewField>(
UtransEditorConfiguration
.EditableAddressFields.Select(field => CreateField(field, true))
Expand All @@ -49,16 +61,21 @@ internal EditorReviewState(DfcSelectionSnapshot selection)
)
);

Cartocode = string.IsNullOrWhiteSpace(utransRoad?.GetText("CARTOCODE"))
Cartocode = string.IsNullOrWhiteSpace(roadValues.GetText("CARTOCODE"))
? "11"
: utransRoad!.GetText("CARTOCODE");
Oneway = string.IsNullOrWhiteSpace(utransRoad?.GetText("ONEWAY"))
: roadValues.GetText("CARTOCODE");
Oneway = string.IsNullOrWhiteSpace(roadValues.GetText("ONEWAY"))
? "0"
: utransRoad!.GetText("ONEWAY");
VerticalLevel = string.IsNullOrWhiteSpace(utransRoad?.GetText("VERT_LEVEL"))
: roadValues.GetText("ONEWAY");
VerticalLevel = string.IsNullOrWhiteSpace(roadValues.GetText("VERT_LEVEL"))
? "0"
: utransRoad!.GetText("VERT_LEVEL");
SpeedLimit = utransRoad?.GetText("SPEED_LMT") ?? string.Empty;
: roadValues.GetText("VERT_LEVEL");
SpeedLimit = string.IsNullOrWhiteSpace(roadValues.GetText("SPEED_LMT"))
? "25"
: roadValues.GetText("SPEED_LMT");
Status = string.IsNullOrWhiteSpace(roadValues.GetText("STATUS"))
? "Active"
: roadValues.GetText("STATUS");
_initialCartocode = Cartocode;
_initialOneway = Oneway;
_initialVerticalLevel = VerticalLevel;
Expand All @@ -83,6 +100,21 @@ AttributeReviewField CreateField(string fieldName, bool isAddressRange) =>
);
}

public string Status
{
get => _status;
set
{
if (_status == value)
{
return;
}

_status = value;
OnPropertyChanged();
}
}

internal DfcSelectionSnapshot? Selection { get; }
public event PropertyChangedEventHandler? PropertyChanged;
public string ChangeLabel => Selection?.ChangeLabel ?? string.Empty;
Expand Down
49 changes: 49 additions & 0 deletions add-in/UGRC.UtransTools/Services/DfcSelectionService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using ArcGIS.Core.Data;
using ArcGIS.Desktop.Framework.Threading.Tasks;
Expand Down Expand Up @@ -64,6 +65,54 @@ internal Task<int> GetRemainingCountAsync(EditorLayerContext layers)
});
}

internal Task<IReadOnlyList<DfcSelectionSnapshot>> LoadSelectedNewRecordsAsync(
EditorLayerContext layers
)
{
return QueuedTask.Run(() =>
{
var selectedObjectIds = layers.DfcResults.GetSelection().GetObjectIDs();
if (selectedObjectIds.Count < 2)
{
return (IReadOnlyList<DfcSelectionSnapshot>)[];
}

using var dfcFeatureClass = layers.DfcResults.GetFeatureClass();
using var countyRoadsFeatureClass = layers.CountyRoads.GetFeatureClass();
var selections = selectedObjectIds
.Select(objectId =>
{
var dfc = ReadFeature(dfcFeatureClass, objectId);
var updateFeatureId = ReadInt64(dfc.Attributes, "UPDATE_FID");
var baseFeatureId = ReadInt64(dfc.Attributes, "BASE_FID");
var changeType = dfc.GetText("CHANGE_TYPE");
return new DfcSelectionSnapshot(
dfc.ObjectId,
updateFeatureId,
baseFeatureId,
changeType,
GetChangeLabel(changeType, baseFeatureId),
dfc,
ReadFeature(countyRoadsFeatureClass, updateFeatureId),
null
);
})
.ToList();

var ineligibleObjectIds = selections
.Where(selection => !selection.IsNotYetCopiedNewRecord)
.Select(selection => selection.ObjectId);
if (ineligibleObjectIds.Any())
{
throw new InvalidOperationException(
$"Only unlinked New DFC_RESULT features can be copied. Ineligible ObjectIDs: {string.Join(", ", ineligibleObjectIds)}."
);
}

return selections;
});
}

private static RoadSnapshot ReadFeature(FeatureClass featureClass, long objectId)
{
var objectIdField = featureClass.GetDefinition().GetObjectIDField();
Expand Down
30 changes: 24 additions & 6 deletions add-in/UGRC.UtransTools/Services/LayerValidationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,33 @@ internal Task<EditorLayerContext> GetRequiredLayersAsync()
);
var layers = map.GetLayersAsFlattenedList().OfType<FeatureLayer>().ToList();
var missingAliases = new List<string>();
var invalidAliases = new List<string>();

FeatureLayer GetLayer(string name)
{
var layer = layers.FirstOrDefault(candidate =>
string.Equals(candidate.Name, name, StringComparison.OrdinalIgnoreCase)
|| string.Equals(
candidate.GetFeatureClass().GetDefinition().GetAliasName(),
name,
StringComparison.OrdinalIgnoreCase
)
);

if (layer is null)
{
missingAliases.Add(name);
}
else
{
try
{
using var featureClass = layer.GetFeatureClass();
if (featureClass is null)
{
invalidAliases.Add(name);
}
}
catch
{
invalidAliases.Add(name);
}
}

return layer!;
}
Expand All @@ -59,6 +70,13 @@ FeatureLayer GetLayer(string name)
);
}

if (invalidAliases.Count > 0)
{
throw new InvalidOperationException(
$"The active map has required layers with broken or unavailable data sources: {string.Join(", ", invalidAliases)}. Repair the layer data sources and try again."
);
}

return new EditorLayerContext(
utransRoads,
countyRoads,
Expand All @@ -82,7 +100,7 @@ internal Task<
StringComparer.OrdinalIgnoreCase
);

foreach (var fieldName in new[] { "CARTOCODE", "ONEWAY", "VERT_LEVEL" })
foreach (var fieldName in new[] { "CARTOCODE", "ONEWAY", "VERT_LEVEL", "STATUS" })
{
var field =
fields.FirstOrDefault(candidate =>
Expand Down
Loading