- if latlon = query.match(/^([NS])\s*(\d{1,3}(\.\d*)?)\W*([EW])\s*(\d{1,3}(\.\d*)?)$/).try(:captures) || # [NSEW] decimal degrees
- query.match(/^(\d{1,3}(\.\d*)?)\s*([NS])\W*(\d{1,3}(\.\d*)?)\s*([EW])$/).try(:captures) # decimal degrees [NSEW]
- params.merge!(nsew_to_decdeg(latlon)).delete(:query)
+ if latlon = query.match(/^(?<ns>[NS])\s*(?<nsd>\d{1,3}(?:\.\d+)?)°?\W*(?<ew>[EW])\s*(?<ewd>\d{1,3}(?:\.\d+)?)°?$/) || # [NSEW] decimal degrees
+ query.match(/^(?<nsd>\d{1,3}(?:\.\d+)?)°?\s*(?<ns>[NS])\W*(?<ewd>\d{1,3}(?:\.\d+)?)°?\s*(?<ew>[EW])$/) || # decimal degrees [NSEW]
+ query.match(/^(?<ns>[NS])\s*(?<nsd>\d{1,3})°?(?:\s*(?<nsm>\d{1,3}(?:\.\d+)?)['′]?)\W*(?<ew>[EW])\s*(?<ewd>\d{1,3})°?(?:\s*(?<ewm>\d{1,3}(?:\.\d+)?)['′]?)$/) || # [NSEW] degrees, decimal minutes
+ query.match(/^(?<nsd>\d{1,3})°?(?:\s*(?<nsm>\d{1,3}(?:\.\d+)?)['′]?)\s*(?<ns>[NS])\W*(?<ewd>\d{1,3})°?(?:\s*(?<ewm>\d{1,3}(?:\.\d+)?)['′]?)\s*(?<ew>[EW])$/) || # degrees, decimal minutes [NSEW]
+ query.match(/^(?<ns>[NS])\s*(?<nsd>\d{1,3})°?\s*(?<nsm>\d{1,2})['′]?(?:\s*(?<nss>\d{1,3}(?:\.\d+)?)["″]?)\W*(?<ew>[EW])\s*(?<ewd>\d{1,3})°?\s*(?<ewm>\d{1,2})['′]?(?:\s*(?<ews>\d{1,3}(?:\.\d+)?)["″]?)$/) || # [NSEW] degrees, minutes, decimal seconds
+ query.match(/^(?<nsd>\d{1,3})°?\s*(?<nsm>\d{1,2})['′]?(?:\s*(?<nss>\d{1,3}(?:\.\d+)?)["″]?)\s*(?<ns>[NS])\W*(?<ewd>\d{1,3})°?\s*(?<ewm>\d{1,2})['′]?(?:\s*(?<ews>\d{1,3}(?:\.\d+)?)["″]?)\s*(?<ew>[EW])$/) # degrees, minutes, decimal seconds [NSEW]
+ params.merge!(to_decdeg(latlon.named_captures)).delete(:query)