-
Notifications
You must be signed in to change notification settings - Fork 0
Verify and document FRFcoord multiple point input support #45
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
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,176 @@ | ||
| # FRFcoord Multiple Point Input Support - Verification | ||
|
|
||
| ## Issue Summary | ||
|
|
||
| **Original Issue**: "Currently `FRFcoord` is limited to single point inputs thereby requiring looping outside of this function." | ||
|
|
||
| **Agent Instructions**: "I think this was solved with most recent update to Transformer class in pyproj. confirm or fix" | ||
|
|
||
| ## Verification Results | ||
|
|
||
| ### ✅ CONFIRMED: `FRFcoord` Already Supports Multiple Input Points | ||
|
|
||
| The `FRFcoord` function and all related coordinate transformation functions in `murgtools.utils.geoprocess` **already support multiple input points** without requiring any code changes. | ||
|
|
||
| This functionality is enabled by pyproj version 3.0+, which provides native array handling in the `Transformer` class. | ||
|
|
||
| ## Key Findings | ||
|
|
||
| ### Supported Input Types | ||
|
|
||
| 1. **Single Point** (original functionality) | ||
| - Input: `FRFcoord(566.93, 515.11)` | ||
| - Output: Scalar/float values | ||
|
|
||
| 2. **NumPy Arrays** (enhanced functionality) | ||
| - Input: `FRFcoord(np.array([100, 200, 300]), np.array([100, 200, 300]))` | ||
| - Output: NumPy arrays of same shape | ||
|
|
||
| 3. **Python Lists** (automatic conversion) | ||
| - Input: `FRFcoord([100, 200, 300], [100, 200, 300])` | ||
| - Output: NumPy arrays (lists automatically converted) | ||
|
|
||
| ### Supported Coordinate Systems | ||
|
|
||
| All coordinate systems support array inputs: | ||
| - **FRF Local Coordinates** (xFRF, yFRF) | ||
| - **NC State Plane** (EPSG:3358) | ||
| - **Lat/Lon** (WGS84, EPSG:4326) | ||
| - **UTM** (Zone 18S) | ||
|
|
||
| ### Functions Supporting Arrays | ||
|
|
||
| All transformation functions support array inputs: | ||
| - `FRFcoord(p1, p2, coordType)` - Universal converter | ||
| - `FRF2ncsp(xFRF, yFRF)` - FRF to State Plane | ||
| - `ncsp2FRF(spE, spN)` - State Plane to FRF | ||
| - `ncsp2LatLon(spE, spN)` - State Plane to Lat/Lon | ||
| - `LatLon2ncsp(lon, lat)` - Lat/Lon to State Plane | ||
| - `LatLon2utm(lat, lon)` - Lat/Lon to UTM | ||
| - `utm2LatLon(utmE, utmN, zn, zl)` - UTM to Lat/Lon | ||
| - `utm2ncsp(utmE, utmN, zn, zl)` - UTM to State Plane | ||
| - `ncsp2utm(easting, northing)` - State Plane to UTM | ||
|
|
||
| ## Usage Examples | ||
|
|
||
| ### Example 1: Converting Multiple FRF Points | ||
|
|
||
| ```python | ||
| import numpy as np | ||
| from murgtools.utils import geoprocess as gp | ||
|
|
||
| # Define multiple points in FRF coordinates | ||
| x_frf = np.array([100.0, 200.0, 300.0, 566.93]) | ||
| y_frf = np.array([100.0, 200.0, 300.0, 515.11]) | ||
|
|
||
| # Convert all points at once (no loop needed!) | ||
| result = gp.FRFcoord(x_frf, y_frf) | ||
|
|
||
| # Access converted coordinates | ||
| print(f"State Plane Easting: {result['StateplaneE']}") | ||
| print(f"State Plane Northing: {result['StateplaneN']}") | ||
| print(f"Latitude: {result['Lat']}") | ||
| print(f"Longitude: {result['Lon']}") | ||
| print(f"UTM Easting: {result['utmE']}") | ||
| print(f"UTM Northing: {result['utmN']}") | ||
| ``` | ||
|
|
||
| ### Example 2: Converting Multiple Lat/Lon Points | ||
|
|
||
| ```python | ||
| import numpy as np | ||
| from murgtools.utils import geoprocess as gp | ||
|
|
||
| # Define multiple points in Lat/Lon | ||
| lon = np.array([-75.75, -75.74, -75.73]) | ||
| lat = np.array([36.17, 36.18, 36.19]) | ||
|
|
||
| # Convert all at once | ||
| result = gp.FRFcoord(lon, lat) | ||
|
|
||
| # Get FRF coordinates for all points | ||
| xFRF = result['xFRF'] | ||
| yFRF = result['yFRF'] | ||
| ``` | ||
|
|
||
| ### Example 3: Using Lists Instead of Arrays | ||
|
|
||
| ```python | ||
| from murgtools.utils import geoprocess as gp | ||
|
|
||
| # Python lists work too! | ||
| x_list = [100.0, 200.0, 300.0] | ||
| y_list = [100.0, 200.0, 300.0] | ||
|
|
||
| result = gp.FRFcoord(x_list, y_list) | ||
| # Output is automatically converted to numpy arrays | ||
| ``` | ||
|
|
||
| ## Test Results | ||
|
|
||
| All 32 existing tests pass, including: | ||
| - Single point conversions | ||
| - Array conversions | ||
| - List conversions | ||
| - Round-trip conversions (FRF → SP → LL → SP → FRF) | ||
| - All coordinate system combinations | ||
|
|
||
| ```bash | ||
| pytest tests/test_geoprocess.py -v | ||
| # Result: 32 passed, 1 warning in 1.57s | ||
| ``` | ||
|
|
||
| ## Technical Details | ||
|
|
||
| ### How It Works | ||
|
|
||
| 1. **pyproj Transformer**: Version 3.0+ of pyproj includes native support for array transformations | ||
| 2. **FRFcoord Logic**: The function automatically detects array inputs using `np.size(p1) > 1` | ||
| 3. **Consistent Behavior**: Uses `.all()` for array comparisons when detecting coordinate types | ||
| 4. **Pass-through**: Arrays are passed through all transformation functions without modification | ||
|
|
||
| ### Implementation in Code | ||
|
|
||
| The key code in `FRFcoord` (lines 321-344 of geoprocess.py): | ||
|
|
||
| ```python | ||
| # convert list to array if needed | ||
| if isinstance(p1, list): | ||
| p1 = np.asarray(p1) | ||
| if isinstance(p2, list): | ||
| p2 = np.asarray(p2) | ||
|
|
||
| # now run checks to see what version of input we have! | ||
| if np.size(p1) > 1: | ||
| LL1 = (np.floor(np.absolute(p1)) == 75).all() | ||
| LL2 = (np.floor(p2) == 36).all() | ||
| SP1 = (p1 > 800000).all() | ||
| SP2 = (p2 > 200000).all() | ||
| # ... etc | ||
| ``` | ||
|
|
||
| The pyproj Transformer handles arrays natively (lines 195-200): | ||
|
|
||
| ```python | ||
| transformer = pyproj.Transformer.from_crs( | ||
| f"epsg:{EPSG}", | ||
| "epsg:4326", | ||
| always_xy=True | ||
| ) | ||
| lon, lat = transformer.transform(spE, spN) # Works with arrays! | ||
| ``` | ||
|
|
||
| ## Conclusion | ||
|
|
||
| ✅ **No code changes required** - the functionality already exists and is fully tested. | ||
|
|
||
| The issue has been resolved by the update to pyproj 3.0+, which provides native array handling in the `Transformer` class. The `FRFcoord` function and all related coordinate transformation functions properly leverage this capability. | ||
|
|
||
| Users can now convert multiple points without external loops, significantly improving performance and code simplicity. | ||
|
|
||
| ## Dependencies | ||
|
|
||
| - **pyproj >= 3.0.0** (current: 3.7.2) | ||
| - **numpy >= 1.20.1** | ||
|
|
||
| These are already specified in `requirements.txt`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,201 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| Demo: FRFcoord Multiple Point Input Support | ||
|
|
||
| This example demonstrates that FRFcoord and related coordinate transformation | ||
| functions in murgtools already support multiple input points without requiring | ||
| external loops. | ||
|
|
||
| This capability is provided by pyproj >= 3.0.0, which has native array support | ||
| in the Transformer class. | ||
| """ | ||
|
|
||
| import numpy as np | ||
| from murgtools.utils import geoprocess as gp | ||
|
|
||
|
|
||
| def demo_single_point(): | ||
| """Demonstrate single point conversion (original functionality).""" | ||
| print("=" * 70) | ||
| print("DEMO 1: Single Point Conversion") | ||
| print("=" * 70) | ||
|
|
||
| # Single point in FRF coordinates | ||
| x = 566.93 | ||
| y = 515.11 | ||
|
|
||
| print(f"\nInput: FRF coordinates") | ||
| print(f" xFRF = {x} m") | ||
| print(f" yFRF = {y} m") | ||
|
|
||
| result = gp.FRFcoord(x, y) | ||
|
|
||
| print(f"\nOutput: All coordinate systems") | ||
| print(f" State Plane E: {result['StateplaneE']:.2f} m") | ||
| print(f" State Plane N: {result['StateplaneN']:.2f} m") | ||
| print(f" Latitude: {result['Lat']:.6f}°") | ||
| print(f" Longitude: {result['Lon']:.6f}°") | ||
| if isinstance(result['utmE'], np.ndarray): | ||
| print(f" UTM E: {result['utmE'][0]:.2f} m") | ||
| print(f" UTM N: {result['utmN'][0]:.2f} m") | ||
| else: | ||
| print(f" UTM E: {result['utmE']:.2f} m") | ||
| print(f" UTM N: {result['utmN']:.2f} m") | ||
| print() | ||
|
|
||
|
|
||
| def demo_array_input(): | ||
| """Demonstrate array input conversion (enhanced functionality).""" | ||
| print("=" * 70) | ||
| print("DEMO 2: Multiple Points - Array Input (No Loop Needed!)") | ||
| print("=" * 70) | ||
|
|
||
| # Multiple points in FRF coordinates | ||
| x_array = np.array([0.0, 100.0, 200.0, 300.0, 566.93]) | ||
| y_array = np.array([0.0, 100.0, 200.0, 300.0, 515.11]) | ||
|
|
||
| print(f"\nInput: {len(x_array)} points as numpy arrays") | ||
| print(f" xFRF = {x_array}") | ||
| print(f" yFRF = {y_array}") | ||
|
|
||
| result = gp.FRFcoord(x_array, y_array) | ||
|
|
||
| print(f"\nOutput: All {len(x_array)} points converted simultaneously") | ||
| print(f" State Plane E: {result['StateplaneE']}") | ||
| print(f" Latitude: {result['Lat']}") | ||
| print() | ||
| print(f"Result types: {type(result['xFRF'])} with shape {result['xFRF'].shape}") | ||
| print() | ||
|
|
||
|
|
||
| def demo_different_coordinate_systems(): | ||
| """Demonstrate array conversion for different input coordinate systems.""" | ||
| print("=" * 70) | ||
| print("DEMO 3: Different Input Coordinate Systems (All Support Arrays)") | ||
| print("=" * 70) | ||
|
|
||
| # Test with Lat/Lon | ||
| print("\n3a. Lat/Lon Input:") | ||
| lon = np.array([-75.75, -75.74, -75.73]) | ||
| lat = np.array([36.17, 36.18, 36.19]) | ||
| result = gp.FRFcoord(lon, lat) | ||
| print(f" Input: {len(lon)} Lat/Lon points") | ||
| print(f" Output: xFRF = {result['xFRF'][:3]}") | ||
|
|
||
| # Test with State Plane | ||
| print("\n3b. State Plane Input:") | ||
| sp_e = np.array([902000.0, 902100.0, 902200.0]) | ||
| sp_n = np.array([274000.0, 274100.0, 274200.0]) | ||
| result = gp.FRFcoord(sp_e, sp_n) | ||
| print(f" Input: {len(sp_e)} State Plane points") | ||
| print(f" Output: xFRF = {result['xFRF']}") | ||
|
|
||
| # Test with Lists (automatically converted) | ||
| print("\n3c. Python List Input (auto-converted to arrays):") | ||
| x_list = [100.0, 200.0, 300.0] | ||
| y_list = [100.0, 200.0, 300.0] | ||
| result = gp.FRFcoord(x_list, y_list) | ||
| print(f" Input: {len(x_list)} points as Python lists") | ||
| print(f" Output: {type(result['xFRF'])} with values {result['xFRF']}") | ||
| print() | ||
|
|
||
|
|
||
| def demo_individual_functions(): | ||
| """Demonstrate that individual transformation functions also support arrays.""" | ||
| print("=" * 70) | ||
| print("DEMO 4: Individual Transformation Functions (All Support Arrays)") | ||
| print("=" * 70) | ||
|
|
||
| # Test FRF2ncsp | ||
| print("\n4a. FRF2ncsp (FRF to State Plane):") | ||
| x = np.array([100.0, 200.0, 300.0]) | ||
| y = np.array([100.0, 200.0, 300.0]) | ||
| result = gp.FRF2ncsp(x, y) | ||
| print(f" Input: {len(x)} FRF points") | ||
| print(f" Output: StateplaneE = {result['StateplaneE']}") | ||
|
|
||
| # Test ncsp2LatLon | ||
| print("\n4b. ncsp2LatLon (State Plane to Lat/Lon):") | ||
| result = gp.ncsp2LatLon(result['StateplaneE'], result['StateplaneN']) | ||
| print(f" Output: Latitude = {result['lat']}") | ||
|
|
||
| # Test LatLon2ncsp | ||
| print("\n4c. LatLon2ncsp (Lat/Lon to State Plane):") | ||
| result2 = gp.LatLon2ncsp(result['lon'], result['lat']) | ||
| print(f" Output: StateplaneE = {result2['StateplaneE']}") | ||
|
|
||
| print("\n✓ Round-trip conversion successful!") | ||
| print() | ||
|
|
||
|
|
||
| def demo_performance_comparison(): | ||
| """Compare performance between loop and array approaches.""" | ||
| print("=" * 70) | ||
| print("DEMO 5: Performance Comparison") | ||
| print("=" * 70) | ||
|
|
||
| import time | ||
|
|
||
| # Create test data | ||
| n = 100 | ||
| x = np.linspace(0, 1000, n) | ||
| y = np.linspace(0, 1000, n) | ||
|
|
||
| print(f"\nConverting {n} points...") | ||
|
|
||
| # Method 1: Loop (old way) | ||
| print("\nMethod 1: Loop (OLD - not necessary)") | ||
| start = time.time() | ||
| results = [] | ||
| for i in range(n): | ||
| results.append(gp.FRFcoord(x[i], y[i])) | ||
| time_loop = time.time() - start | ||
| print(f" Time: {time_loop:.4f} seconds") | ||
|
|
||
| # Method 2: Array (new way) | ||
| print("\nMethod 2: Array (NEW - already supported!)") | ||
| start = time.time() | ||
| result = gp.FRFcoord(x, y) | ||
| time_array = time.time() - start | ||
| print(f" Time: {time_array:.4f} seconds") | ||
|
|
||
| # Compare | ||
| speedup = time_loop / time_array | ||
| print(f"\n✓ Array input is {speedup:.1f}x faster!") | ||
| print() | ||
|
|
||
|
|
||
| def main(): | ||
| """Run all demonstrations.""" | ||
| print("\n") | ||
| print("╔" + "═" * 68 + "╗") | ||
| print("║" + " " * 68 + "║") | ||
| print("║" + " FRFcoord Multiple Point Input Support - Demo".center(68) + "║") | ||
| print("║" + " " * 68 + "║") | ||
| print("╚" + "═" * 68 + "╝") | ||
| print() | ||
|
|
||
| demo_single_point() | ||
| demo_array_input() | ||
| demo_different_coordinate_systems() | ||
| demo_individual_functions() | ||
| demo_performance_comparison() | ||
|
|
||
| print("=" * 70) | ||
| print("SUMMARY") | ||
| print("=" * 70) | ||
| print() | ||
| print("✓ FRFcoord supports single point AND multiple point inputs") | ||
| print("✓ No external loops needed for batch conversions") | ||
| print("✓ Array input is significantly faster than looping") | ||
| print("✓ All coordinate systems supported: FRF, State Plane, Lat/Lon, UTM") | ||
| print("✓ All transformation functions support arrays") | ||
| print() | ||
| print("This functionality is provided by pyproj >= 3.0.0") | ||
| print("No code changes needed - it already works!") | ||
| print("=" * 70) | ||
| print() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.