Skip to content

Conversation

@cs-util
Copy link

@cs-util cs-util commented Dec 23, 2025

No description provided.

…tionality for setting reference distances and measuring
…istance input modal for reference distance entry with validation and unit selection
…es for scale and measure markers and labels in the UI
…d rely on conversion function for improved clarity
…ility and integrate into distance validation
…structure. I also utilized createScaleModeState() and createMeasureModeState() from the logic module to ensure consistent initial states.

Logic Integration: Updated all calls to pure functions (like handleScaleModePoint or computeMeasurement) to pass only the logic sub-object.
UI Management: Updated all Leaflet-related code to store and retrieve markers/lines from the ui sub-object
…l referenceDistance and verify it remains unchanged after handleDistanceModalCancel() is called
…nitSelect in index.js to include logic that updates all pinned measurement labels.

Exported setupEventHandlers: Added setupEventHandlers to the __testables export in index.js to facilitate integration testing.
Added Integration Test: Added a new test case to index.scale.test.js:268-301 to verify that pinned measurement labels are correctly updated when the global unit changes.
Improved Mocks: Updated the L.divIcon mock in index.scale.test.js:35 to correctly capture and return options for verification in tests
Creating a centralized createDistanceLabelHtml helper function in index.js:754 that handles the generation of distance labels with optional "Pin" or "Delete" buttons.
Updating drawReferenceVisualization, updateMeasureLabel, and pinCurrentMeasurement to use this new helper.
Replacing the regex-based replacement in pinCurrentMeasurement with a clean re-render of the label using the helper function
@gemini-code-assist
Copy link

Summary of Changes

Hello @cs-util, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a significant new feature to Snap2Map, empowering users with a flexible and accurate way to measure distances directly on their imported maps and photos. By allowing the definition of a manual reference scale, the application can now provide immediate, GPS-independent measurements, which are prioritized as 'ground truth' over GPS data. The changes encompass a complete user experience for setting and utilizing this manual scale, supporting various measurement units, and are built upon a thoroughly tested and refactored codebase for improved maintainability and reliability.

Highlights

  • Manual Reference Distance: Introduced the ability for users to define a manual reference distance on an imported map or photo, enabling immediate distance measurements without relying on GPS data.
  • Hybrid Calibration: Implemented a system where the manual reference acts as 'ground truth', taking precedence over GPS-derived scales for local measurements and allowing it to constrain or validate GPS calibration results.
  • Comprehensive UI/UX: Added new 'Set Scale' and 'Measure' modes with intuitive interaction flows, including draggable markers for refinement, real-time feedback, and support for multiple pinned measurements.
  • Unit Selection & Persistence: Users can now select their preferred display units (meters, feet, feet & inches), and these preferences, along with the reference distance, are persisted across sessions.
  • Robust Backend Logic & Testing: Developed a new fitSimilarityFixedScale algorithm for the calibration pipeline and extracted UI logic into pure state machine functions, all backed by extensive unit, integration, and property-based tests achieving high code coverage.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This is an impressive pull request that introduces a significant and well-designed feature for manual scaling and measurement. The implementation is of very high quality, demonstrating excellent software engineering practices. The code is modular, with a clear separation between pure state logic and UI-side effects, which is a great pattern. The testing strategy is exceptionally thorough, including unit, integration, and even property-based tests, ensuring a high degree of confidence in the new functionality. My feedback is minor and focuses on aligning the design documentation with the final implementation to ensure it remains a reliable source of truth for future development.

Comment on lines +103 to +108
case 'ft-in': {
const totalInches = meters * METERS_TO_FEET * 12;
const feet = Math.floor(totalInches / 12);
const inches = Math.round(totalInches % 12);
return `${feet}' ${inches}"`;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The example implementation for formatDistance in the ft-in case has a potential bug. Math.round(totalInches % 12) can result in 12 if totalInches % 12 is, for example, 11.5. This would lead to an incorrect output like 5' 12". The actual implementation in src/scale/scale.js correctly handles this edge case. It would be best to update this documentation to reflect the correct, more robust implementation.

Suggested change
case 'ft-in': {
const totalInches = meters * METERS_TO_FEET * 12;
const feet = Math.floor(totalInches / 12);
const inches = Math.round(totalInches % 12);
return `${feet}' ${inches}"`;
}
case 'ft-in': {
const totalInches = meters * METERS_TO_FEET * 12;
const feet = Math.floor(totalInches / 12);
const inches = Math.round(totalInches % 12);
if (inches === 12) {
return `${feet + 1}' 0\"`;
}
return `${feet}' ${inches}\"`;
}

Comment on lines +146 to +164
/**
* Extracts the scale (meters per pixel) from a calibration result.
* For similarity transforms, scale = sqrt(a² + b²) where matrix is [a, b, tx; -b, a, ty].
* Note: The calibration matrix maps pixels → geo coordinates, so this gives geo-units/pixel.
* For lat/lon, additional conversion to meters is needed based on latitude.
*
* @param {Object} calibrationResult - Result from calibrateMap()
* @returns {number|null} - Scale in meters per pixel, or null if not extractable
*/
function getMetersPerPixelFromCalibration(calibrationResult) {
if (!calibrationResult || !calibrationResult.matrix) return null;
const { a, b } = calibrationResult.matrix;
const geoUnitsPerPixel = Math.sqrt(a * a + b * b);
// Convert degrees to meters (approximate, using center latitude)
// 1 degree ≈ 111,320 meters at equator, adjusted by cos(lat)
const centerLat = calibrationResult.centerLat || 0;
const metersPerDegree = 111320 * Math.cos(centerLat * Math.PI / 180);
return geoUnitsPerPixel * metersPerDegree;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The documentation and example implementation for getMetersPerPixelFromCalibration appear to be outdated. The code describes a conversion from "geo-units/pixel" (implying degrees) to "meters/pixel" using the latitude. However, the application's calibration pipeline (calibrateMap) works with ENU coordinates, which are already in meters. The actual implementation in src/scale/scale.js correctly reflects this by directly extracting the scale from the similarity or affine model without any degree-to-meter conversion. To avoid confusion, this section of the documentation should be updated to match the simpler, correct implementation.

/**
 * Extracts the scale (meters per pixel) from a calibration result.
 * For similarity transforms, the scale is a direct property of the model.
 * For affine transforms, an average scale is computed.
 * Note: The calibration matrix maps pixels → ENU coordinates (meters),
 * so the scale is already in meters/pixel.
 * 
 * @param {Object} calibrationResult - Result from calibrateMap()
 * @returns {number|null} - Scale in meters per pixel, or null if not extractable
 */
function getMetersPerPixelFromCalibration(calibrationResult) {
  if (!calibrationResult || calibrationResult.status !== 'ok' || !calibrationResult.model) {
    return null;
  }
  
  const { model } = calibrationResult;

  if (model.type === 'similarity' && typeof model.scale === 'number') {
    return Math.abs(model.scale);
  }

  if (model.type === 'affine' && model.matrix) {
    const { a, b, c, d } = model.matrix;
    const scaleX = Math.hypot(a, b);
    const scaleY = Math.hypot(c, d);
    return (scaleX + scaleY) / 2;
  }

  return null;
}

Achieved 100% Statement, Branch, Function, and Line coverage across all files in the src directory.
Added comprehensive test cases to transformations.test.js to cover:
Degenerate cases in fitHomography.
Singular matrices in invertHomography.
Jacobian calculations for affine and homography transforms.
Unsupported transform types in applyTransform and applyInverseTransform.
Added test cases to calibrator.test.js to cover:
Automatic origin computation in calibrateMap.
Edge cases in evaluateModel (e.g., infinite RMSE).
Defensive branches in accuracyRingRadiusPixels.
Code Refinement:

Simplified a redundant null check in accuracyRingRadiusPixels within calibrator.js to improve branch coverage and code clarity.
@cs-util cs-util closed this Dec 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants