diff --git a/iphone/Maps/Bookmarks/BookmarksList/BookmarksListViewController.swift b/iphone/Maps/Bookmarks/BookmarksList/BookmarksListViewController.swift index dbe95c1a6f..3d857496f8 100644 --- a/iphone/Maps/Bookmarks/BookmarksList/BookmarksListViewController.swift +++ b/iphone/Maps/Bookmarks/BookmarksList/BookmarksListViewController.swift @@ -7,11 +7,11 @@ final class BookmarksListViewController: MWMViewController { private var canEdit = false @IBOutlet private var tableView: UITableView! - @IBOutlet private var searchBar: UISearchBar! @IBOutlet private var toolBar: UIToolbar! @IBOutlet private var sortToolbarItem: UIBarButtonItem! @IBOutlet private var moreToolbarItem: UIBarButtonItem! - + private let searchController = UISearchController(searchResultsController: nil) + private lazy var infoViewController: BookmarksListInfoViewController = { let infoViewController = BookmarksListInfoViewController() infoViewController.delegate = self @@ -30,7 +30,16 @@ final class BookmarksListViewController: MWMViewController { sortToolbarItem.setTitleTextAttributes(toolbarItemAttributes, for: .normal) moreToolbarItem.setTitleTextAttributes(toolbarItemAttributes, for: .normal) sortToolbarItem.title = L("sort") - searchBar.placeholder = L("search_in_the_list") + + extendedLayoutIncludesOpaqueBars = true + searchController.searchBar.placeholder = L("search_in_the_list") + searchController.obscuresBackgroundDuringPresentation = false + searchController.hidesNavigationBarDuringPresentation = alternativeSizeClass(iPhone: true, iPad: false) + searchController.searchBar.delegate = self + searchController.searchBar.applyTheme() + navigationItem.searchController = searchController + navigationItem.hidesSearchBarWhenScrolling = false + cellStrategy.registerCells(tableView) cellStrategy.cellCheckHandler = { [weak self] (viewModel, index, checked) in self?.presenter.checkItem(in: viewModel, at: index, checked: checked) @@ -157,13 +166,11 @@ extension BookmarksListViewController: UITableViewDelegate { extension BookmarksListViewController: UISearchBarDelegate { func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { searchBar.setShowsCancelButton(true, animated: true) - navigationController?.setNavigationBarHidden(true, animated: true) presenter.activateSearch() } func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { searchBar.setShowsCancelButton(false, animated: true) - navigationController?.setNavigationBarHidden(false, animated: true) presenter.deactivateSearch() } diff --git a/iphone/Maps/Bookmarks/BookmarksList/BookmarksListViewController.xib b/iphone/Maps/Bookmarks/BookmarksList/BookmarksListViewController.xib index e7d1728495..ed2639b95d 100644 --- a/iphone/Maps/Bookmarks/BookmarksList/BookmarksListViewController.xib +++ b/iphone/Maps/Bookmarks/BookmarksList/BookmarksListViewController.xib @@ -11,7 +11,6 @@ - @@ -23,23 +22,6 @@ - - - - - - - - - - - - - - - - - @@ -77,19 +59,13 @@ - - - - - - - + diff --git a/iphone/Maps/Bookmarks/Categories/BMCView/BMCViewController.swift b/iphone/Maps/Bookmarks/Categories/BMCView/BMCViewController.swift index b4a26d3731..274af6795a 100644 --- a/iphone/Maps/Bookmarks/Categories/BMCView/BMCViewController.swift +++ b/iphone/Maps/Bookmarks/Categories/BMCView/BMCViewController.swift @@ -34,6 +34,7 @@ final class BMCViewController: MWMViewController { override func viewDidLoad() { super.viewDidLoad() + view.styleName = "PressBackground" viewModel = BMCDefaultViewModel() } diff --git a/iphone/Maps/Core/Theme/GlobalStyleSheet.swift b/iphone/Maps/Core/Theme/GlobalStyleSheet.swift index 1ee1021bd6..2cba499355 100644 --- a/iphone/Maps/Core/Theme/GlobalStyleSheet.swift +++ b/iphone/Maps/Core/Theme/GlobalStyleSheet.swift @@ -30,8 +30,9 @@ class GlobalStyleSheet: IStyleSheet { theme.add(styleName: "SearchBar") { (s) -> (Void) in s.backgroundColor = colors.white s.barTintColor = colors.primary - s.tintColor = UIColor.white - s.fontColor = colors.blackSecondaryText + s.fontColor = colors.blackPrimaryText + s.fontColorDetailed = UIColor.white + s.tintColor = colors.blackSecondaryText } theme.add(styleName: "NavigationBar") { (s) -> (Void) in diff --git a/iphone/Maps/Core/Theme/Renderers/UISearchBarRenderer.swift b/iphone/Maps/Core/Theme/Renderers/UISearchBarRenderer.swift index ada3393185..89182b2ce3 100644 --- a/iphone/Maps/Core/Theme/Renderers/UISearchBarRenderer.swift +++ b/iphone/Maps/Core/Theme/Renderers/UISearchBarRenderer.swift @@ -27,7 +27,23 @@ class UISearchBarRenderer: UIViewRenderer { var searchTextField: UITextField? if #available(iOS 13, *) { searchTextField = control.searchTextField + control.searchTextField.layer.cornerCurve = .continuous } + + // Default search bar implementation adds the grey transparent image for background. This code removes it and updates the corner radius. This is not working on iPad designed for mac. + if #available(iOS 14.0, *), ProcessInfo.processInfo.isiOSAppOnMac { + } else { + control.setSearchFieldBackgroundImage(UIImage(), for: .normal) + } + + searchTextField?.layer.cornerRadius = 8 + searchTextField?.layer.masksToBounds = true + + // Placeholder color + if let placeholder = searchTextField?.placeholder { + searchTextField?.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [.foregroundColor: UIColor.gray]) + } + if let backgroundColor = style.backgroundColor { searchTextField?.backgroundColor = backgroundColor } @@ -37,16 +53,28 @@ class UISearchBarRenderer: UIViewRenderer { control.setBackgroundImage(barTintColor.getImage(), for: position, barMetrics: .default) control.backgroundColor = barTintColor } - if let tintColor = style.tintColor { - control.tintColor = tintColor - } if let font = style.font { searchTextField?.font = font } if let fontColor = style.fontColor { searchTextField?.textColor = fontColor - searchTextField?.leftView?.tintColor = fontColor - searchTextField?.tintColor = fontColor + } + if let fontColorDetailed = style.fontColorDetailed { + // Cancel button color + control.tintColor = fontColorDetailed + } + if let tintColor = style.tintColor { + searchTextField?.leftView?.tintColor = tintColor + // Placeholder indicator color + searchTextField?.tintColor = tintColor + // Clear button image + let clearButtonImage: UIImage? + if #available(iOS 13.0, *) { + clearButtonImage = UIImage(named: "ic_clear")?.withRenderingMode(.alwaysTemplate).withTintColor(tintColor) + } else { + clearButtonImage = UIImage(named: "ic_search_clear_14") + } + control.setImage(clearButtonImage, for: .clear, state: .normal) } } diff --git a/iphone/Maps/Core/Theme/Renderers/UITextFieldRenderer.swift b/iphone/Maps/Core/Theme/Renderers/UITextFieldRenderer.swift index 26f15d68d6..4868a46599 100644 --- a/iphone/Maps/Core/Theme/Renderers/UITextFieldRenderer.swift +++ b/iphone/Maps/Core/Theme/Renderers/UITextFieldRenderer.swift @@ -19,6 +19,13 @@ extension UITextField { class UITextFieldRenderer { class func render(_ control: UITextField, style: Style) { + if let cornerRadius = style.cornerRadius { + control.layer.cornerRadius = cornerRadius + control.clipsToBounds = true + if #available(iOS 13.0, *) { + control.layer.cornerCurve = .continuous + } + } var placeholderAttributes = [NSAttributedString.Key : Any]() if let backgroundColor = style.backgroundColor { control.backgroundColor = backgroundColor @@ -29,7 +36,6 @@ class UITextFieldRenderer { } if let fontColor = style.fontColor { control.textColor = fontColor - } if let tintColor = style.tintColor { control.tintColor = tintColor diff --git a/iphone/Maps/Core/Theme/SearchStyleSheet.swift b/iphone/Maps/Core/Theme/SearchStyleSheet.swift index ab2fabe407..978d7d88fe 100644 --- a/iphone/Maps/Core/Theme/SearchStyleSheet.swift +++ b/iphone/Maps/Core/Theme/SearchStyleSheet.swift @@ -56,9 +56,11 @@ class SearchStyleSheet: IStyleSheet { } theme.add(styleName: "SearchSearchTextField") { (s) -> Void in - s.fontColor = colors.blackSecondaryText + s.fontColor = colors.blackPrimaryText s.backgroundColor = colors.white s.tintColor = colors.blackSecondaryText + s.cornerRadius = 8.0 + s.barTintColor = colors.primary } theme.add(styleName: "SearchSearchTextFieldIcon") { (s) -> Void in diff --git a/iphone/Maps/Images.xcassets/Search/ic_clear.symbolset/Contents.json b/iphone/Maps/Images.xcassets/Search/ic_clear.symbolset/Contents.json new file mode 100644 index 0000000000..0be0ab1451 --- /dev/null +++ b/iphone/Maps/Images.xcassets/Search/ic_clear.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "xmark.circle.fill.svg", + "idiom" : "universal" + } + ] +} diff --git a/iphone/Maps/Images.xcassets/Search/ic_clear.symbolset/xmark.circle.fill.svg b/iphone/Maps/Images.xcassets/Search/ic_clear.symbolset/xmark.circle.fill.svg new file mode 100644 index 0000000000..620a552982 --- /dev/null +++ b/iphone/Maps/Images.xcassets/Search/ic_clear.symbolset/xmark.circle.fill.svg @@ -0,0 +1,197 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.3.0 + Requires Xcode 13 or greater + Generated from xmark.circle.fill + Typeset at 100 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iphone/Maps/UI/Downloader/DownloadMapsViewController.swift b/iphone/Maps/UI/Downloader/DownloadMapsViewController.swift index e9e172c78b..8d6d169e93 100644 --- a/iphone/Maps/UI/Downloader/DownloadMapsViewController.swift +++ b/iphone/Maps/UI/Downloader/DownloadMapsViewController.swift @@ -27,7 +27,7 @@ class DownloadMapsViewController: MWMViewController { // MARK: - Properties - private var searchBar: UISearchBar = UISearchBar() + private var searchController = UISearchController(searchResultsController: nil) var dataSource: IDownloaderDataSource! @objc var mode: MWMMapDownloaderMode = .downloaded private var skipCountryEvent = false @@ -77,13 +77,15 @@ class DownloadMapsViewController: MWMViewController { navigationItem.rightBarButtonItem = addMapsButton } noMapsContainer.isHidden = !dataSource.isEmpty || Storage.shared().downloadInProgress() - - if dataSource.isRoot { - searchBar.placeholder = L("downloader_search_field_hint") - searchBar.delegate = self - // TODO: Fix the height and centering of the searchBar, it's very tricky. - navigationItem.titleView = searchBar - } + extendedLayoutIncludesOpaqueBars = true + searchController.searchBar.placeholder = L("downloader_search_field_hint") + searchController.searchBar.delegate = self + searchController.obscuresBackgroundDuringPresentation = false + searchController.hidesNavigationBarDuringPresentation = alternativeSizeClass(iPhone: true, iPad: false) + searchController.searchBar.applyTheme() + navigationItem.searchController = searchController + navigationItem.hidesSearchBarWhenScrolling = false + configButtons() } @@ -279,7 +281,7 @@ extension DownloadMapsViewController: UITableViewDataSource { cell = placeCell } cell.mode = dataSource.isSearching ? .available : mode - cell.config(nodeAttrs, searchQuery: searchBar.text) + cell.config(nodeAttrs, searchQuery: searchController.searchBar.text) cell.delegate = self return cell } @@ -355,7 +357,7 @@ extension DownloadMapsViewController: UITableViewDelegate { extension DownloadMapsViewController: UIScrollViewDelegate { func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - searchBar.resignFirstResponder() + searchController.searchBar.resignFirstResponder() } } @@ -412,7 +414,7 @@ extension DownloadMapsViewController: StorageObserver { guard let downloaderCell = cell as? MWMMapDownloaderTableViewCell else { continue } if downloaderCell.nodeAttrs.countryId != countryId { continue } guard let indexPath = tableView.indexPath(for: downloaderCell) else { return } - downloaderCell.config(dataSource.item(at: indexPath), searchQuery: searchBar.text) + downloaderCell.config(dataSource.item(at: indexPath), searchQuery: searchController.searchBar.text) } } @@ -454,14 +456,6 @@ extension DownloadMapsViewController: UISearchBarDelegate { } } -// MARK: - UIBarPositioningDelegate - -extension DownloadMapsViewController: UIBarPositioningDelegate { - func position(for bar: UIBarPositioning) -> UIBarPosition { - .topAttached - } -} - // MARK: - DownloadAllViewDelegate extension DownloadMapsViewController: DownloadAllViewDelegate { diff --git a/iphone/Maps/UI/Downloader/DownloadedMapsDataSource.swift b/iphone/Maps/UI/Downloader/DownloadedMapsDataSource.swift index 77d939e116..61534c524f 100644 --- a/iphone/Maps/UI/Downloader/DownloadedMapsDataSource.swift +++ b/iphone/Maps/UI/Downloader/DownloadedMapsDataSource.swift @@ -41,7 +41,7 @@ extension DownloadedMapsDataSource: IDownloaderDataSource { var title: String { guard let parentCountryId = parentCountryId else { - return "" // Root Downloader dialog displays UISearchBar instead of title. + return L("download_maps") } return Storage.shared().name(forCountry: parentCountryId) } diff --git a/iphone/Maps/UI/Search/MWMSearchView.xib b/iphone/Maps/UI/Search/MWMSearchView.xib index 5cdbe54275..336dffe9a4 100644 --- a/iphone/Maps/UI/Search/MWMSearchView.xib +++ b/iphone/Maps/UI/Search/MWMSearchView.xib @@ -1,9 +1,9 @@ - + - + @@ -19,7 +19,7 @@ - + @@ -35,10 +35,10 @@ - - + + - + @@ -51,12 +51,12 @@ - + @@ -79,7 +79,7 @@ - + @@ -88,7 +88,7 @@ - - - + @@ -1042,15 +1021,36 @@ + + + diff --git a/platform/utm_mgrs_utils.cpp b/platform/utm_mgrs_utils.cpp index 1157ab3f57..28d250e8b9 100644 --- a/platform/utm_mgrs_utils.cpp +++ b/platform/utm_mgrs_utils.cpp @@ -8,55 +8,59 @@ namespace utm_mgrs_utils { -namespace -{ +using namespace std; +using namespace math; // To use constexpr math::pi +using namespace base; // To use base::DegToRad and base::RadToDeg +using namespace ms; // To use ms::LatLon + struct UTMPoint { double easting; double northing; - int zoneNumber; - char zoneLetter; + int zone_number; + char zone_letter; + }; -double constexpr K0 = 0.9996; // The scale factor at the central meridian. -double constexpr E = 0.00669438; // The square of the eccentricity for the WGS84 ellipsoid. -double constexpr E2 = E * E; -double constexpr E3 = E2 * E; -double constexpr E_P2 = E / (1 - E); -double constexpr R = 6378137.0; // The Earth equitorial radius for the WGS84 ellipsoid. +constexpr double K0 = 0.9996; // The scale factor at the central meridian. +constexpr double E = 0.00669438; // The square of the eccentricity for the WGS84 ellipsoid. +constexpr double E2 = E * E; +constexpr double E3 = E2 * E; +constexpr double E_P2 = E / (1 - E); +constexpr double R = 6378137.0; // The Earth equitorial radius for the WGS84 ellipsoid. -double constexpr SQRT_E = 0.996647189; // sqrt(1 - E) -double constexpr _E = (1 - SQRT_E) / (1 + SQRT_E); -double constexpr _E2 = _E * _E; -double constexpr _E3 = _E2 * _E; -double constexpr _E4 = _E3 * _E; -double constexpr _E5 = _E4 * _E; +constexpr double SQRT_E = 0.996647189; // sqrt(1 - E) +constexpr double _E = (1 - SQRT_E) / (1 + SQRT_E); +constexpr double _E2 = _E * _E; +constexpr double _E3 = _E2 * _E; +constexpr double _E4 = _E3 * _E; +constexpr double _E5 = _E4 * _E; -double constexpr M1 = (1.0 - E / 4.0 - 3.0 * E2 / 64.0 - 5.0 * E3 / 256.0); -double constexpr M2 = (3.0 * E / 8.0 + 3.0 * E2 / 32.0 + 45.0 * E3 / 1024.0); -double constexpr M3 = (15.0 * E2 / 256.0 + 45.0 * E3 / 1024.0); -double constexpr M4 = (35.0 * E3 / 3072.0); +constexpr double M1 = (1.0 - E / 4.0 - 3.0 * E2 / 64.0 - 5.0 * E3 / 256.0); +constexpr double M2 = (3.0 * E / 8.0 + 3.0 * E2 / 32.0 + 45.0 * E3 / 1024.0); +constexpr double M3 = (15.0 * E2 / 256.0 + 45.0 * E3 / 1024.0); +constexpr double M4 = (35.0 * E3 / 3072.0); -double constexpr P2 = (3.0 / 2.0 * _E - 27.0 / 32.0 * _E3 + 269.0 / 512.0 * _E5); -double constexpr P3 = (21.0 / 16.0 * _E2 - 55.0 / 32.0 * _E4); -double constexpr P4 = (151.0 / 96.0 * _E3 - 417.0 / 128.0 * _E5); -double constexpr P5 = (1097.0 / 512.0 * _E4); +constexpr double P2 = (3.0 / 2.0 * _E - 27.0 / 32.0 * _E3 + 269.0 / 512.0 * _E5); +constexpr double P3 = (21.0 / 16.0 * _E2 - 55.0 / 32.0 * _E4); +constexpr double P4 = (151.0 / 96.0 * _E3 - 417.0 / 128.0 * _E5); +constexpr double P5 = (1097.0 / 512.0 * _E4); -int constexpr kInvalidEastingNorthing = -1; +constexpr int kInvalidEastingNorthing = -1; -std::string_view constexpr kZoneLetters = "CDEFGHJKLMNPQRSTUVWXX"; +const string ZONE_LETTERS = "CDEFGHJKLMNPQRSTUVWXX"; -std::string_view constexpr kSetOriginColumnLetters = "SAJSAJ"; -std::string_view constexpr kSetOriginRowLetters = "FAFAFA"; +const int NUM_100K_SETS = 6; +const int SET_ORIGIN_COLUMN_LETTERS[] = { 'S', 'A', 'J', 'S', 'A', 'J' }; +const int SET_ORIGIN_ROW_LETTERS[] = { 'F', 'A', 'F', 'A', 'F', 'A' }; // Returns angle in radians to be between -π and π. double NormalizeAngle(double value) { - using math::pi; if (value < -pi) - value += - 2 * pi * (static_cast(value - pi) / (2 * pi)); + value += - 2 * pi * ((int)(value - pi) / (2 * pi)); else if (value > pi) - value -= 2 * pi * (static_cast(value + pi) / (2 * pi)); + value -= 2 * pi * ((int)(value + pi) / (2 * pi)); return value; } @@ -69,101 +73,97 @@ int LatLonToZoneNumber(double lat, double lon) { if (lon < 9.0) return 31; - if (lon < 21.0) + else if (lon < 21.0) return 33; - if (lon < 33.0) + else if (lon < 33.0) return 35; - if (lon < 42.0) + else if (lon < 42.0) return 37; } - return static_cast((lon + 180.0) / 6.0) + 1; + return int((lon + 180.0) / 6.0) + 1; } -std::optional LatitudeToZoneLetter(double lat) +optional LatitudeToZoneLetter(double lat) { if (-80.0 <= lat && lat <= 84.0) - { - auto const index = static_cast(lat + 80.0) >> 3; - ASSERT_LESS(index, kZoneLetters.size(), ()); - return kZoneLetters[index]; - } - return {}; + return ZONE_LETTERS[int(lat + 80.0) >> 3]; + else + return nullopt; } -int ZoneNumberToCentralLon(int zoneNumber) +int ZoneNumberToCentralLon(int zone_number) { - return (zoneNumber - 1) * 6 - 180 + 3; + return (zone_number - 1) * 6 - 180 + 3; } -// Main algorithm. Formulas source: https://github.com/Turbo87/utm +// Main algorithm. Formulars source: https://github.com/Turbo87/utm UTMPoint LatLonToUtm(double lat, double lon) { - using std::sin, std::cos, std::sqrt; + double const lat_rad = DegToRad(lat); + double const lat_sin = sin(lat_rad); + double const lat_cos = cos(lat_rad); - double const latRad = base::DegToRad(lat); - double const latSin = sin(latRad); - double const latCos = cos(latRad); + double const lat_tan = lat_sin / lat_cos; + double const lat_tan2 = lat_tan * lat_tan; + double const lat_tan4 = lat_tan2 * lat_tan2; - double const latTan = latSin / latCos; - double const latTan2 = latTan * latTan; - double const latTan4 = latTan2 * latTan2; + int const zone_number = LatLonToZoneNumber(lat, lon); + char const zone_letter = (lat >= 0.0) ? 'N' : 'S'; - int const zoneNumber = LatLonToZoneNumber(lat, lon); - char const zoneLetter = (lat >= 0.0) ? 'N' : 'S'; + double const lon_rad = DegToRad(lon); + double const central_lon = ZoneNumberToCentralLon(zone_number); + double const central_lon_rad = DegToRad(central_lon); - double const lonRad = base::DegToRad(lon); - double const centralLon = ZoneNumberToCentralLon(zoneNumber); - double const centralLonRad = base::DegToRad(centralLon); + double const n = R / sqrt(1 - E * lat_sin * lat_sin); + double const c = E_P2 * lat_cos * lat_cos; - double const n = R / sqrt(1.0 - E * latSin * latSin); - double const c = E_P2 * latCos * latCos; - - double const a = latCos * NormalizeAngle(lonRad - centralLonRad); + double const a = lat_cos * NormalizeAngle(lon_rad - central_lon_rad); double const a2 = a * a; double const a3 = a2 * a; double const a4 = a3 * a; double const a5 = a4 * a; double const a6 = a5 * a; - double const m = R * (M1 * latRad - - M2 * sin(2 * latRad) + - M3 * sin(4 * latRad) - - M4 * sin(6 * latRad)); + double const m = R * (M1 * lat_rad - + M2 * sin(2 * lat_rad) + + M3 * sin(4 * lat_rad) - + M4 * sin(6 * lat_rad)); double const easting = K0 * n * (a + - a3 / 6 * (1 - latTan2 + c) + - a5 / 120 * (5 - 18 * latTan2 + latTan4 + 72 * c - 58 * E_P2)) + 500000.0; + a3 / 6 * (1 - lat_tan2 + c) + + a5 / 120 * (5 - 18 * lat_tan2 + lat_tan4 + 72 * c - 58 * E_P2)) + 500000.0; - double northing = K0 * (m + n * latTan * (a2 / 2 + - a4 / 24 * (5 - latTan2 + 9 * c + 4 * c * c) + - a6 / 720 * (61 - 58 * latTan2 + latTan4 + 600 * c - 330 * E_P2))); + double northing = K0 * (m + n * lat_tan * (a2 / 2 + + a4 / 24 * (5 - lat_tan2 + 9 * c + 4 * c * c) + + a6 / 720 * (61 - 58 * lat_tan2 + lat_tan4 + 600 * c - 330 * E_P2))); if (lat < 0.0) northing += 10000000.0; - return {easting, northing, zoneNumber, zoneLetter}; + return {easting, northing, zone_number, zone_letter}; } // Generate UTM string from UTM point parameters. -std::string UTMtoStr(UTMPoint const & point) +string UTMtoStr(UTMPoint point) { // Easting and northing are rounded to nearest integer. Because of that in some cases // last 5 digits of UTM and MGRS coordinates could differ (inaccuracy is no more then 1 meter). // Some UTM converters truncate easting and northing instead of rounding. Consider this option. - return std::to_string(point.zoneNumber) + point.zoneLetter + ' ' + - std::to_string(static_cast(std::round(point.easting))) + ' ' + - std::to_string(static_cast(std::round(point.northing))); + + return to_string(point.zone_number) + string(1, point.zone_letter) + " " + \ + to_string(int(round(point.easting))) + " " + \ + to_string(int(round(point.northing))); } // Build 2 chars string with MGRS 100k designator. -std::string Get100kId(double easting, double northing, int zoneNumber) +string get_100k_id(double easting, double northing, int zone_number) { - int const set = zoneNumber % kSetOriginColumnLetters.size(); - int const setColumn = easting / 100000; - int const setRow = static_cast(northing / 100000) % 20; + int set = zone_number % NUM_100K_SETS; + int setColumn = ((int) easting / 100000); + int setRow = ((int) northing / 100000) % 20; - int const colOrigin = kSetOriginColumnLetters[set]; - int const rowOrigin = kSetOriginRowLetters[set]; + int colOrigin = SET_ORIGIN_COLUMN_LETTERS[set]; + int rowOrigin = SET_ORIGIN_ROW_LETTERS[set]; int colInt = colOrigin + setColumn - 1; int rowInt = rowOrigin + setRow; @@ -209,48 +209,52 @@ std::string Get100kId(double easting, double northing, int zoneNumber) if (rowInt > 'V') rowInt = rowInt - 'V' + 'A' - 1; - return {static_cast(colInt), static_cast(rowInt)}; + string twoLetter = {char(colInt), char(rowInt)}; + + return twoLetter; } // Convert UTM point parameters to MGRS parameters. Additional 2 char code is deducted. // Easting and northing parameters are reduced to 5 digits. -std::string UTMtoMgrsStr(UTMPoint const & point, int precision) +string UTMtoMgrsStr(UTMPoint point, int precision) { - if (point.zoneLetter == 'Z') + if (point.zone_letter == 'Z') return "Latitude limit exceeded"; - auto const eastingStr = strings::to_string_width(static_cast(point.easting), precision + 1); - auto northingStr = strings::to_string_width(static_cast(point.northing), precision + 1); + else + { + string eastingStr = strings::to_string_width((long)point.easting, precision+1); + string northingStr = strings::to_string_width((long)point.northing, precision+1); - if (northingStr.size() > 6) - northingStr = northingStr.substr(northingStr.size() - 6); + if (northingStr.size() > 6) + northingStr = northingStr.substr(northingStr.size() - 6); - return strings::to_string_width(point.zoneNumber, 2) + point.zoneLetter + ' ' + - Get100kId(point.easting, point.northing, point.zoneNumber) + ' ' + - eastingStr.substr(1, precision) + ' ' + - northingStr.substr(1, precision); + return strings::to_string_width((long) point.zone_number, 2) + point.zone_letter + " " + \ + get_100k_id(point.easting, point.northing, point.zone_number) + " " + \ + eastingStr.substr(1, precision) + " " + \ + northingStr.substr(1, precision); + } } -} // namespace // Convert UTM parameters to lat,lon for WSG 84 ellipsoid. // If UTM parameters are valid lat and lon references are used to output calculated coordinates. -// Otherwise function returns empty optional. -std::optional UTMtoLatLon(int easting, int northing, int zoneNumber, char zoneLetter) +// Otherwise function returns 'false'. +std::optional UTMtoLatLon(int easting, int northing, int zone_number, char zone_letter) { - if (zoneNumber < 1 || zoneNumber > 60) - return {}; + if (zone_number < 1 || zone_number > 60) + return nullopt; if (easting < 100000 || easting >= 1000000) - return {}; + return nullopt; if (northing < 0 || northing > 10000000) - return {}; + return nullopt; - if (zoneLetter < 'C' || zoneLetter > 'X' || zoneLetter == 'I' || zoneLetter == 'O') - return {}; + if (zone_letter<'C' || zone_letter>'X' || zone_letter == 'I' || zone_letter == 'O') + return nullopt; - bool const northern = (zoneLetter >= 'N'); - double const x = easting - 500000.0; - double y = northing; + bool northern = (zone_letter >= 'N'); + double x = (double)easting - 500000.0; + double y = (double)northing; if (!northern) y -= 10000000.0; @@ -298,20 +302,17 @@ std::optional UTMtoLatLon(int easting, int northing, int zoneNumber, d3 / 6.0 * (1.0 + 2.0 * p_tan2 + c) + d5 / 120.0 * (5.0 - 2.0 * c + 28.0 * p_tan2 - 3.0 * c2 + 8.0 * E_P2 + 24.0 * p_tan4)) / p_cos; - longitude = NormalizeAngle(longitude + base::DegToRad(static_cast(ZoneNumberToCentralLon(zoneNumber)))); + longitude = NormalizeAngle(longitude + DegToRad((double)ZoneNumberToCentralLon(zone_number))); - return ms::LatLon(base::RadToDeg(latitude), base::RadToDeg(longitude)); + return LatLon(RadToDeg(latitude), RadToDeg(longitude)); } -namespace -{ -// Given the first letter from a two-letter MGRS 100k zone, and given the -// MGRS table set for the zone number, figure out the easting value that -// should be added to the other, secondary easting value. -int SquareCharToEasting(char e, size_t set) -{ - ASSERT_LESS(set, kSetOriginColumnLetters.size(), ()); - int curCol = kSetOriginColumnLetters[set]; + +/* Given the first letter from a two-letter MGRS 100k zone, and given the + * MGRS table set for the zone number, figure out the easting value that + * should be added to the other, secondary easting value.*/ +int SquareCharToEasting(char e, int set) { + int curCol = SET_ORIGIN_COLUMN_LETTERS[set]; int eastingValue = 100000; bool rewindMarker = false; @@ -349,7 +350,7 @@ int SquareCharToNorthing(char n, int set) if (n > 'V') return kInvalidEastingNorthing; - int curRow = kSetOriginRowLetters[set]; + int curRow = SET_ORIGIN_ROW_LETTERS[set]; int northingValue = 0; bool rewindMarker = false; @@ -377,62 +378,105 @@ int SquareCharToNorthing(char n, int set) // Get minimum northing value of a MGRS zone. int ZoneToMinNorthing(char zoneLetter) { + int northing; switch (zoneLetter) { - case 'C': return 1100000; - case 'D': return 2000000; - case 'E': return 2800000; - case 'F': return 3700000; - case 'G': return 4600000; - case 'H': return 5500000; - case 'J': return 6400000; - case 'K': return 7300000; - case 'L': return 8200000; - case 'M': return 9100000; - case 'N': return 0; - case 'P': return 800000; - case 'Q': return 1700000; - case 'R': return 2600000; - case 'S': return 3500000; - case 'T': return 4400000; - case 'U': return 5300000; - case 'V': return 6200000; - case 'W': return 7000000; - case 'X': return 7900000; - default: return kInvalidEastingNorthing; + case 'C': + northing = 1100000; + break; + case 'D': + northing = 2000000; + break; + case 'E': + northing = 2800000; + break; + case 'F': + northing = 3700000; + break; + case 'G': + northing = 4600000; + break; + case 'H': + northing = 5500000; + break; + case 'J': + northing = 6400000; + break; + case 'K': + northing = 7300000; + break; + case 'L': + northing = 8200000; + break; + case 'M': + northing = 9100000; + break; + case 'N': + northing = 0; + break; + case 'P': + northing = 800000; + break; + case 'Q': + northing = 1700000; + break; + case 'R': + northing = 2600000; + break; + case 'S': + northing = 3500000; + break; + case 'T': + northing = 4400000; + break; + case 'U': + northing = 5300000; + break; + case 'V': + northing = 6200000; + break; + case 'W': + northing = 7000000; + break; + case 'X': + northing = 7900000; + break; + default: + northing = kInvalidEastingNorthing; } + + return northing; } -} // namespace // Convert MGRS parameters to UTM parameters and then use UTM to lat,lon conversion. -std::optional MGRStoLatLon(int easting, int northing, int zoneCode, char zoneLetter, char squareCode[2]) +std::optional MGRStoLatLon(int easting, int northing, int zone_code, char zone_letter, char square_code[2]) { // Convert easting and northing according to zone_code and square_code - if (zoneCode < 1 || zoneCode > 60) - return {}; + if (zone_code < 1 || zone_code > 60) + return nullopt; - if (zoneLetter <= 'B' || zoneLetter >= 'Y' || zoneLetter == 'I' || zoneLetter == 'O') - return {}; + if (zone_letter <= 'B' || zone_letter >= 'Y' || zone_letter == 'I' || zone_letter == 'O') + return nullopt; - auto const set = zoneCode % kSetOriginColumnLetters.size(); + int set = zone_code % NUM_100K_SETS; - auto const char1 = squareCode[0]; - auto const char2 = squareCode[1]; + char char1 = square_code[0]; + char char2 = square_code[1]; if (char1 < 'A' || char2 < 'A' || char1 > 'Z' || char2 > 'Z' || char1 == 'I' || char2 == 'I' || char1 == 'O' || char2 == 'O') - return {}; + return nullopt; - int const east100k = SquareCharToEasting(char1, set); + int east100k = SquareCharToEasting(char1, set); if (east100k == kInvalidEastingNorthing) - return {}; + return nullopt; int north100k = SquareCharToNorthing(char2, set); if (north100k == kInvalidEastingNorthing) - return {}; + return nullopt; - int const minNorthing = ZoneToMinNorthing(zoneLetter); + int minNorthing = ZoneToMinNorthing(zone_letter); if (minNorthing == kInvalidEastingNorthing) - return {}; + return nullopt; while (north100k < minNorthing) north100k += 2000000; @@ -440,11 +484,11 @@ std::optional MGRStoLatLon(int easting, int northing, int zoneCode, easting += east100k; northing += north100k; - return UTMtoLatLon(easting, northing, zoneCode, zoneLetter); + return UTMtoLatLon(easting, northing, zone_code, zone_letter); } // Convert lat,lon for WGS84 ellipsoid to MGRS string. -std::string FormatMGRS(double lat, double lon, int precision) +string FormatMGRS(double lat, double lon, int precision) { if (precision > 5) precision = 5; @@ -459,10 +503,10 @@ std::string FormatMGRS(double lat, double lon, int precision) UTMPoint mgrsp = LatLonToUtm(lat, lon); // Need to set the right letter for the latitude. - auto const maybeZone = LatitudeToZoneLetter(lat); + auto maybeZone = LatitudeToZoneLetter(lat); if (maybeZone) { - mgrsp.zoneLetter = *maybeZone; + mgrsp.zone_letter = maybeZone.value(); return UTMtoMgrsStr(mgrsp, precision); } @@ -470,14 +514,16 @@ std::string FormatMGRS(double lat, double lon, int precision) } // Convert lat,lon for WGS84 ellipsoid to UTM string. -std::string FormatUTM(double lat, double lon) +string FormatUTM(double lat, double lon) { if (lat <= -80 || lat > 84) return {}; // Latitude limit exceeded. if (lon <= -180 || lon > 180) return {}; // Longitude limit exceeded. - return UTMtoStr(LatLonToUtm(lat, lon)); + UTMPoint utm = LatLonToUtm(lat, lon); + return UTMtoStr(utm); } + } // namespace utm_mgrs_utils diff --git a/platform/utm_mgrs_utils.hpp b/platform/utm_mgrs_utils.hpp index 48a2cdf406..0598095e86 100644 --- a/platform/utm_mgrs_utils.hpp +++ b/platform/utm_mgrs_utils.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include "geometry/latlon.hpp" @@ -38,6 +37,6 @@ std::string FormatMGRS(double lat, double lon, int prec); std::optional UTMtoLatLon(int easting, int northing, int zone_code, char zone_letter); // Covevrt MGRS coordinates to Lat Lon. If parameters are invalid function returns false -std::optional MGRStoLatLon(int easting, int northing, int zoneCode, char zone_letter, char squareCode[2]); +std::optional MGRStoLatLon(int easting, int northing, int zone_code, char zone_letter, char square_code[2]); } // namespace utm_mgrs_utils diff --git a/search/search_tests/utm_mgrs_coords_match_test.cpp b/search/search_tests/utm_mgrs_coords_match_test.cpp index 49ba78f6f1..7a7894fac1 100644 --- a/search/search_tests/utm_mgrs_coords_match_test.cpp +++ b/search/search_tests/utm_mgrs_coords_match_test.cpp @@ -4,16 +4,18 @@ #include "base/math.hpp" -#include +#include namespace utm_mgrs_coords_match_test { -void TestAlmostEqual(std::optional const & maybeLatLon, double expectedLat, double expectedLon) -{ - TEST(maybeLatLon, ()); +using namespace search; - // We expect the results to be quite precise. - static double constexpr kEps = 1e-5; +// We expect the results to be quite precise. +double const kEps = 1e-5; + +void TestAlmostEqual(std::optional maybeLatLon, double expectedLat, double expectedLon) +{ + TEST(maybeLatLon.has_value(), ()); auto const actualLat = maybeLatLon->m_lat; TEST(base::AlmostEqualAbsOrRel(actualLat, expectedLat, kEps), ("Lat is not close", actualLat, expectedLat)); @@ -24,14 +26,9 @@ void TestAlmostEqual(std::optional const & maybeLatLon, double expec UNIT_TEST(MatchUTMCoords) { - using search::MatchUTMCoords; - - TEST(!MatchUTMCoords(" "), ()); - // Extra spaces shouldn't break format - TEST(MatchUTMCoords("15 N 500000 4649776"), ()); - TEST(MatchUTMCoords("15 N 500000 4649776"), ()); - TEST(MatchUTMCoords("15 N 500000 4649776"), ()); + TEST(MatchUTMCoords("15 N 500000 4649776").has_value(), ()); + TEST(MatchUTMCoords("15 N 500000 4649776").has_value(), ()); TestAlmostEqual(MatchUTMCoords("15N 500000 4649776"), 42.0, -93.0); TestAlmostEqual(MatchUTMCoords("15 N 500000 4649776"), 42.0, -93.0); @@ -43,81 +40,75 @@ UNIT_TEST(MatchUTMCoords) UNIT_TEST(MatchUTMCoords_False) { - using search::MatchUTMCoords; - - TEST(!MatchUTMCoords("2 1st"), ()); - TEST(!MatchUTMCoords("15N5000004649776"), ()); + TEST(!MatchUTMCoords("2 1st").has_value(), ()); + TEST(!MatchUTMCoords("15N5000004649776").has_value(), ()); // Wrong zone number (first two digits) - TEST(!MatchUTMCoords("0X 476594 9328501"), ()); - TEST(!MatchUTMCoords("0 X 476594 9328501"), ()); - TEST(!MatchUTMCoords("61N 294409 5628898"), ()); - TEST(!MatchUTMCoords("61 N 294409 5628898"), ()); + TEST(!MatchUTMCoords("0X 476594 9328501").has_value(), ()); + TEST(!MatchUTMCoords("0 X 476594 9328501").has_value(), ()); + TEST(!MatchUTMCoords("61N 294409 5628898").has_value(), ()); + TEST(!MatchUTMCoords("61 N 294409 5628898").has_value(), ()); // Wrong zone letter - TEST(!MatchUTMCoords("25I 500000 4649776"), ()); - TEST(!MatchUTMCoords("25 I 500000 4649776"), ()); - TEST(!MatchUTMCoords("25O 500000 4649776"), ()); - TEST(!MatchUTMCoords("25 O 500000 4649776"), ()); - TEST(!MatchUTMCoords("5A 500000 4649776"), ()); - TEST(!MatchUTMCoords("5 A 500000 4649776"), ()); - TEST(!MatchUTMCoords("7B 500000 4649776"), ()); - TEST(!MatchUTMCoords("7 B 500000 4649776"), ()); + TEST(!MatchUTMCoords("25I 500000 4649776").has_value(), ()); + TEST(!MatchUTMCoords("25 I 500000 4649776").has_value(), ()); + TEST(!MatchUTMCoords("25O 500000 4649776").has_value(), ()); + TEST(!MatchUTMCoords("25 O 500000 4649776").has_value(), ()); + TEST(!MatchUTMCoords("5A 500000 4649776").has_value(), ()); + TEST(!MatchUTMCoords("5 A 500000 4649776").has_value(), ()); + TEST(!MatchUTMCoords("7B 500000 4649776").has_value(), ()); + TEST(!MatchUTMCoords("7 B 500000 4649776").has_value(), ()); // easting out of range (must be between 100,000 m and 999,999 m) - TEST(!MatchUTMCoords("19S 999 6360877"), ()); - TEST(!MatchUTMCoords("19S 99999 6360877"), ()); - TEST(!MatchUTMCoords("19S 1000000 6360877"), ()); - TEST(!MatchUTMCoords("19S 2000000 6360877"), ()); + TEST(!MatchUTMCoords("19S 999 6360877").has_value(), ()); + TEST(!MatchUTMCoords("19S 99999 6360877").has_value(), ()); + TEST(!MatchUTMCoords("19S 1000000 6360877").has_value(), ()); + TEST(!MatchUTMCoords("19S 2000000 6360877").has_value(), ()); // northing out of range (must be between 0 m and 10,000,000 m) - TEST(!MatchUTMCoords("30N 476594 10000001"), ()); - TEST(!MatchUTMCoords("30N 476594 20000000"), ()); + TEST(!MatchUTMCoords("30N 476594 10000001").has_value(), ()); + TEST(!MatchUTMCoords("30N 476594 20000000").has_value(), ()); } UNIT_TEST(MatchMGRSCoords_parsing) { - using search::MatchMGRSCoords; - - TEST(MatchMGRSCoords("30N YF 67993 00000"), ()); - TEST(MatchMGRSCoords("30N YF 67993 00000 "), ()); - TEST(MatchMGRSCoords("30N YF 67993 00000 "), ()); - TEST(MatchMGRSCoords("30N YF 67993 00000 "), ()); - TEST(MatchMGRSCoords("30NYF 67993 00000"), ()); - TEST(MatchMGRSCoords("30NYF 67993 00000 "), ()); - TEST(MatchMGRSCoords("30NYF 67993 00000 "), ()); - TEST(MatchMGRSCoords("30NYF67993 00000"), ()); - TEST(MatchMGRSCoords("30NYF67993 00000 "), ()); - TEST(MatchMGRSCoords("30NYF67993 00000 "), ()); - TEST(MatchMGRSCoords("30NYF6799300000"), ()); - TEST(MatchMGRSCoords("30NYF6799300000 "), ()); + TEST(MatchMGRSCoords("30N YF 67993 00000").has_value(), ()); + TEST(MatchMGRSCoords("30N YF 67993 00000 ").has_value(), ()); + TEST(MatchMGRSCoords("30N YF 67993 00000 ").has_value(), ()); + TEST(MatchMGRSCoords("30N YF 67993 00000 ").has_value(), ()); + TEST(MatchMGRSCoords("30NYF 67993 00000").has_value(), ()); + TEST(MatchMGRSCoords("30NYF 67993 00000 ").has_value(), ()); + TEST(MatchMGRSCoords("30NYF 67993 00000 ").has_value(), ()); + TEST(MatchMGRSCoords("30NYF67993 00000").has_value(), ()); + TEST(MatchMGRSCoords("30NYF67993 00000 ").has_value(), ()); + TEST(MatchMGRSCoords("30NYF67993 00000 ").has_value(), ()); + TEST(MatchMGRSCoords("30NYF6799300000").has_value(), ()); + TEST(MatchMGRSCoords("30NYF6799300000 ").has_value(), ()); // Wrong number of digits - TEST(!MatchMGRSCoords("30NYF 679930000 "), ()); - TEST(!MatchMGRSCoords("30NYF 679930000"), ()); + TEST(!MatchMGRSCoords("30NYF 679930000 ").has_value(), ()); + TEST(!MatchMGRSCoords("30NYF 679930000").has_value(), ()); - TEST(!MatchMGRSCoords("30N YF 693 23020"), ()); - TEST(!MatchMGRSCoords("30N YF 693 23 "), ()); + TEST(!MatchMGRSCoords("30N YF 693 23020").has_value(), ()); + TEST(!MatchMGRSCoords("30N YF 693 23 ").has_value(), ()); // Invalid zone - TEST(!MatchMGRSCoords("30 FF 693 230"), ()); - TEST(!MatchMGRSCoords("30A YF 693 230"), ()); - TEST(!MatchMGRSCoords("30Z YF 693 230"), ()); - TEST(!MatchMGRSCoords("30Z F 693 230"), ()); - TEST(!MatchMGRSCoords("30Z 3F 693 230"), ()); - TEST(!MatchMGRSCoords("30Z K? 693 230"), ()); - TEST(!MatchMGRSCoords("30Z IB 693 230"), ()); - TEST(!MatchMGRSCoords("30Z DO 693 230"), ()); + TEST(!MatchMGRSCoords("30 FF 693 230").has_value(), ()); + TEST(!MatchMGRSCoords("30A YF 693 230").has_value(), ()); + TEST(!MatchMGRSCoords("30Z YF 693 230").has_value(), ()); + TEST(!MatchMGRSCoords("30Z F 693 230").has_value(), ()); + TEST(!MatchMGRSCoords("30Z 3F 693 230").has_value(), ()); + TEST(!MatchMGRSCoords("30Z K? 693 230").has_value(), ()); + TEST(!MatchMGRSCoords("30Z IB 693 230").has_value(), ()); + TEST(!MatchMGRSCoords("30Z DO 693 230").has_value(), ()); // Wrong easting or northing - TEST(!MatchMGRSCoords("30NYF 679_3 00000"), ()); - TEST(!MatchMGRSCoords("30NYF 6930&000"), ()); + TEST(!MatchMGRSCoords("30NYF 679_3 00000").has_value(), ()); + TEST(!MatchMGRSCoords("30NYF 6930&000").has_value(), ()); } UNIT_TEST(MatchMGRSCoords_convert) { - using search::MatchMGRSCoords; - TestAlmostEqual(MatchMGRSCoords("30N YF 67993 00000"), 0.000000, -0.592330); TestAlmostEqual(MatchMGRSCoords("31N BA 00000 00000"), 0.000000, 0.304980); TestAlmostEqual(MatchMGRSCoords("32R LR 00000 00000"), 27.107980, 6.982490); @@ -159,6 +150,7 @@ UNIT_TEST(MatchMGRSCoords_convert) TestAlmostEqual(MatchMGRSCoords("24P YV 49519 98153"), 13.541120, -36.694510); TestAlmostEqual(MatchMGRSCoords("31K BA 64726 02675"), -18.051740, 0.777360); TestAlmostEqual(MatchMGRSCoords("31N BA 32007 00000"), 0.000000, 0.592330); + } } // namespace utm_mgrs_coords_match_test diff --git a/search/utm_mgrs_coords_match.cpp b/search/utm_mgrs_coords_match.cpp index 2535feb2f6..36d1972d6a 100644 --- a/search/utm_mgrs_coords_match.cpp +++ b/search/utm_mgrs_coords_match.cpp @@ -4,67 +4,74 @@ #include "platform/utm_mgrs_utils.hpp" +#include #include +#include +#include #include "base/math.hpp" #include "base/string_utils.hpp" namespace search { -static std::string_view constexpr kSpaceChars = " \t\r\n"; + +using namespace std; + +string const kSpaceChars = " \t\r"; + // Matches 2 digits zone code. Returns end position of matched chars or string::npos if no match. -static size_t MatchZoneCode(std::string const & query, int & zoneCode) +size_t MatchZoneCode(string const & query, int & zone_code) { auto const pos = query.find_first_not_of(kSpaceChars); - if (pos == std::string::npos || query.size() < pos + 2) - return std::string::npos; + if (query.size() - pos < 2) + return string::npos; char const dig1 = query[pos]; - char const dig2 = query[pos + 1]; - if (dig1 > '9' || dig1 < '0' || dig2 > '9' || dig2 < '0') - return std::string::npos; + char const dig2 = query[pos+1]; + if (dig1 < '0' || dig1 > '9' || dig2 < '0' || dig2 > '9') + return string::npos; - zoneCode = (dig1 - '0') * 10 + (dig2 - '0'); + zone_code = (dig1 - '0') * 10 + (dig2 - '0'); return pos + 2; } // Matches zone letter ignoring spaces. Returns end position of matched chars or string::npos if no match. -size_t MatchZoneLetter(std::string const & query, char & zoneLetter, size_t startPos) +size_t MatchZoneLetter(string const & query, char & zone_letter, size_t startPos) { auto const pos = query.find_first_not_of(kSpaceChars, startPos); - if (pos == std::string::npos || query.size() == pos) - return std::string::npos; + if (query.size() == pos) + return string::npos; char const l = query[pos]; if (l < 'A' || l > 'Z') - return std::string::npos; + return string::npos; - zoneLetter = l; + zone_letter = l; return pos + 1; } // Matches long number ignoring spaces. Returns end position of matched chars or string::npos if no match. -size_t MatchInt(std::string const & query, int & value, size_t startPos) +size_t MatchInt(string const & query, int & value, size_t startPos) { auto pos = query.find_first_not_of(kSpaceChars, startPos); - if (pos == std::string::npos || query.size() == pos) - return std::string::npos; + if (query.size() == pos) + return string::npos; int n = 0; while (pos < query.size()) { - char const ch = query[pos]; + char ch = query[pos]; if (ch >= '0' && ch <= '9') // Found digit { n = n * 10 + (ch - '0'); - pos++; + pos ++; } - else if (kSpaceChars.find(ch) != std::string::npos) // Found space char matching end of the number + else if (kSpaceChars.find(ch) != string::npos) // Found space char matching end of the number break; else // Found invalid char - return std::string::npos; + return string::npos; } value = n; @@ -75,30 +82,29 @@ size_t MatchInt(std::string const & query, int & value, size_t startPos) // Return true if parsed successfully or false otherwise. // See utm_mgrs_coords_match_test.cpp for sample UTM strings // TODO: Add support of Polar regions. E.g. "A 1492875 2040624" -// TODO: Support additional formats listed here: https://www.killetsoft.de/t_0901_e.htm -std::optional MatchUTMCoords(std::string const & query) +std::optional MatchUTMCoords(string const & query) { int easting, northing; - int zoneCode; - char zoneLetter; + int zone_code; + char zone_letter; - size_t pos = MatchZoneCode(query, zoneCode); - if (pos == std::string::npos) - return {}; + size_t pos = MatchZoneCode(query, zone_code); + if (pos == string::npos) + return nullopt; - pos = MatchZoneLetter(query, zoneLetter, pos); - if (pos == std::string::npos) - return {}; + pos = MatchZoneLetter(query, zone_letter, pos); + if (pos == string::npos) + return nullopt; pos = MatchInt(query, easting, pos); - if (pos == std::string::npos) - return {}; + if (pos == string::npos) + return nullopt; pos = MatchInt(query, northing, pos); - if (pos == std::string::npos) - return {}; + if (pos == string::npos) + return nullopt; - return utm_mgrs_utils::UTMtoLatLon(easting, northing, zoneCode, zoneLetter); + return utm_mgrs_utils::UTMtoLatLon(easting, northing, zone_code, zone_letter); } // Parse MGRS format "(\d\d\W)\s*(\W\W)\s*(\d+)\s*(\d+)" and converts it to lat,lon. @@ -107,17 +113,17 @@ std::optional MatchUTMCoords(std::string const & query) // TODO: Add support of Polar regions. E.g. "A SN 92875 40624" std::optional MatchMGRSCoords(std::string const & query) { - int zoneCode; - char zoneLetter; - char squareCode[2]; - std::string eastingStr; - std::string northingStr; + int zone_code; + char zone_letter; + char square_code[2]; + string eastingStr; + string northingStr; int32_t easting; int32_t northing; strings::SimpleTokenizer it(query, " \t\r"); if (!it) - return {}; + return nullopt; auto token = std::string(*it); // Parse 2 digit zone code and 1 char zone letter @@ -126,32 +132,32 @@ std::optional MatchMGRSCoords(std::string const & query) char dig1 = token[0]; char dig2 = token[1]; if (dig1 < '0' || dig1 > '9' || dig2 < '0' || dig2 > '9') - return {}; + return nullopt; - zoneCode = (dig1 - '0') * 10 + (dig2 - '0'); - if (zoneCode < 1 || zoneCode > 60) - return {}; + zone_code = (dig1 - '0') * 10 + (dig2 - '0'); + if (zone_code < 1 || zone_code > 60) + return nullopt; - zoneLetter = token[2]; + zone_letter = token[2]; token = token.substr(3); } else - return {}; + return nullopt; // Read next token if needed. if (token.size() == 0) { ++it; if (!it) - return {}; + return nullopt; token = std::string(*it); } // Parse 2 chars zone code. if (token.size() >= 2) { - squareCode[0] = token[0]; - squareCode[1] = token[1]; + square_code[0] = token[0]; + square_code[1] = token[1]; token = token.substr(2); } @@ -160,7 +166,7 @@ std::optional MatchMGRSCoords(std::string const & query) { ++it; if (!it) - return {}; + return nullopt; token = std::string(*it); } @@ -177,7 +183,7 @@ std::optional MatchMGRSCoords(std::string const & query) { // eastingStr contains both easting and northing. Let's split if (eastingStr.size() % 2 != 0) - return {}; + return nullopt; size_t const eastingSize = eastingStr.size() / 2; northingStr = eastingStr.substr(eastingSize); @@ -185,10 +191,10 @@ std::optional MatchMGRSCoords(std::string const & query) } if (eastingStr.size() != northingStr.size() || eastingStr.size() > 5 || northingStr.size() > 5) - return {}; + return nullopt; if (!strings::to_int32(eastingStr, easting)) - return {}; + return nullopt; if (eastingStr.size() < 5) { uint64_t const decShift = 5 - eastingStr.size(); @@ -196,14 +202,14 @@ std::optional MatchMGRSCoords(std::string const & query) } if (!strings::to_int32(northingStr, northing)) - return {}; + return nullopt; if (northingStr.size() < 5) { uint64_t const decShift = 5 - northingStr.size(); northing *= base::PowUint(10, decShift); } - return utm_mgrs_utils::MGRStoLatLon(easting, northing, zoneCode, zoneLetter, squareCode); + return utm_mgrs_utils::MGRStoLatLon(easting, northing, zone_code, zone_letter, square_code); } }