How to make an API that gives city data?

I am wondering how to make an API that lets the user enter a city and then shows them data.
For example, if someone entered Huntington, West Virginia or Huntington, WV, they would see the data for Huntington West Virginia.
I have an API developed already, but it always evaluates for Huntington and nothing else. The notebook could also use the IP address as the default and show weather data and geographical data and astronomical data, for example, but so far I haven’t been able to figure this out.
How can I make an API that uses the IP address of the visitor to make a map for example?
I have tried changing my IP address with the virtual private network Private Internet Access but it still defaults to Huntington.

Here’s my current code:

I have tried using Defer, Delayed, FindGeoLocation, Here, APIFunction, and FormFunction, as well as reading the documentation and reading An Elementary Introduction to Wolfram Language 2nd edition chapter 36 creating websites and apps.

CloudDeploy[
  Delayed[
  Block[{city}, city = GeoNearest["City", FindGeoLocation[]]; 
   Grid[{{Style["Gravity potential data", "Subsubsection"], 
      QuantityForm[
       UnitConvert[
        GeogravityModelData[EntityValue[city, "Position"], 
         "Potential"], "SIBase"], "Abbreviation"]}, {Style[
       "Magnetic magnitude data", "Subsubsection"], 
      QuantityForm[
       UnitConvert[
        GeomagneticModelData[EntityValue[city, "Position"], 
         "Magnitude"], "SIBase"], "Abbreviation"]}, {Style[
       "Magnetic north component data", "Subsubsection"], 
      QuantityForm[
       UnitConvert[
        GeomagneticModelData[EntityValue[city, "Position"], 
         "NorthComponent"], "SIBase"], "Abbreviation"]}, {Style[
       "Magnetic east component data", "Subsubsection"], 
      QuantityForm[
       UnitConvert[
        GeomagneticModelData[EntityValue[city, "Position"], 
         "EastComponent"], "SIBase"], "Abbreviation"]}, {Style[
       "Magnetic down component data", "Subsubsection"], 
      QuantityForm[
       UnitConvert[
        GeomagneticModelData[EntityValue[city, "Position"], 
         "DownComponent"], "SIBase"], "Abbreviation"]}, {Style[
       "Magnetic horizontal component data", "Subsubsection"], 
      QuantityForm[
       UnitConvert[
        GeomagneticModelData[EntityValue[city, "Position"], 
         "HorizontalComponent"], "SIBase"], "Abbreviation"]}, {Style[
       "Magnetic declination data", "Subsubsection"], 
      QuantityForm[
       UnitConvert[
        GeomagneticModelData[EntityValue[city, "Position"], 
         "Declination"], "SIBase"], "Abbreviation"]}, {Style[
       "Magnetic inclination data", "Subsubsection"], 
      QuantityForm[
       UnitConvert[
        GeogravityModelData[EntityValue[city, "Position"], 
         "Inclination"], "SIBase"], "Abbreviation"]}, {Style[
       "Magnetic potential data", "Subsubsection"], 
      QuantityForm[
       UnitConvert[
        GeomagneticModelData[EntityValue[city, "Position"], 
         "Potential"], "SIBase"], "Abbreviation"]}, {Style[
       "Cloud Cover Fraction Data", "Subsubsection"], 
      WeatherData[EntityValue[city, "Position"], 
       "CloudCoverFraction"]}, {Style["Dew Point Data", 
       "Subsubsection"], 
      QuantityForm[
       UnitConvert[
        WeatherData[EntityValue[city, "Position"], "DewPoint"], "SI"],
        "Abbreviation"]}, {Style["Pressure Data", "Subsubsection"], 
      QuantityForm[
       UnitConvert[
        WeatherData[EntityValue[city, "Position"], "Pressure"], "SI"],
        "Abbreviation"]}, {Style["Visibility Data", "Subsubsection"], 
      QuantityForm[
       UnitConvert[
        WeatherData[EntityValue[city, "Position"], "Visibility"], 
        "SI"], "Abbreviation"]}, {Style["Wind Chill Data", 
       "Subsubsection"], 
      QuantityForm[
       UnitConvert[
        WeatherData[EntityValue[city, "Position"], "WindChill"], 
        "SI"], "Abbreviation"]}, {Style[
       "Sun Position Right Ascension and Declination without \
atmospheric refraction corrections", "Subsubsection"], 
      SunPosition[CelestialSystem -> "Equatorial", 
       AltitudeMethod -> "TrueAltitude"]}, {Style[
       "Moon Position Right Ascension and Declination without \
atmospheric refraction corrections", "Subsubsection"], 
      MoonPosition[CelestialSystem -> "Equatorial", 
       AltitudeMethod -> "TrueAltitude"]}, {Style[
       "Sun Position Right Ascension and Declination with atmospheric \
refraction corrections", "Subsubsection"], 
      SunPosition[CelestialSystem -> "Equatorial", 
       AltitudeMethod -> "ApparentAltitude"]}, {Style[
       "Moon Position Right Ascension and Declination with \
atmospheric refraction corrections", "Subsubsection"], 
      MoonPosition[CelestialSystem -> "Equatorial", 
       AltitudeMethod -> "ApparentAltitude"]}, {Style[
       "Sun Position Azimuth and Altitude without atmospheric \
refraction corrections", "Subsubsection"], 
      SunPosition[CelestialSystem -> "Horizon", 
       AltitudeMethod -> "TrueAltitude"]}, {Style[
       "Moon Position Azimuth and Altitude without atmospheric \
refraction corrections", "Subsubsection"], 
      MoonPosition[CelestialSystem -> "Horizon", 
       AltitudeMethod -> "TrueAltitude"]}, {Style[
       "Sun Position Azimuth and Altitude with atmospheric refraction \
corrections", "Subsubsection"], 
      SunPosition[CelestialSystem -> "Horizon", 
       AltitudeMethod -> "ApparentAltitude"]}, {Style[
       "Moon Position Azimuth and Altitude with atmospheric \
refraction corrections", "Subsubsection"], 
      MoonPosition[CelestialSystem -> "Horizon", 
       AltitudeMethod -> "ApparentAltitude"]}, {Style[
       "Next upcoming sunrise", "Subsubsection"], 
      DateString[Sunrise[], "DateTime"]}, {Style[
       "Next upcoming sunset", "Subsubsection"], 
      DateString[Sunset[], "DateTime"]}, {Style[
       "Current moon phase with positive or negative sign", 
       "Subsubsection"], 
      MoonPhase["SignedFraction"]}, {Style["Current moon phase", 
       "Subsubsection"], 
      MoonPhase["Name"]["Name"]}, {Style["Current moon phase icon", 
       "Subsubsection"], 
      MoonPhase["Icon"]}, {Style["Next annular solar eclipse", 
       "Subsubsection"], 
      DateString[SolarEclipse[EclipseType -> "Annular"], 
       "DateTime"]}, {Style["Next hybrid solar eclipse", 
       "Subsubsection"], 
      DateString[SolarEclipse[EclipseType -> "Hybrid"], 
       "DateTime"]}, {Style["Next partial solar eclipse", 
       "Subsubsection"], 
      DateString[SolarEclipse[EclipseType -> "Partial"], 
       "DateTime"]}, {Style["Next total solar eclipse", 
       "Subsubsection"], 
      DateString[SolarEclipse[EclipseType -> "Total"], 
       "DateTime"]}, {Style["Next partial lunar eclipse", 
       "Subsubsection"], 
      DateString[LunarEclipse[EclipseType -> "Partial"], 
       "DateTime"]}, {Style["Solar Time", "Subsubsection"], 
      ToString[SolarTime[]]}, {Style["Sidereal time Time", 
       "Subsubsection"], 
      ToString[SiderealTime[]]}, {Style["Julian date", 
       "Subsubsection"], 
      Floor@JulianDate[]}, {Style["Map", "Subsubsection"], 
      GeoGraphics[EntityValue[city, "Position"], 
       GeoBackground -> "VectorClassic", ImageSize -> Large]}, {Style[
       "City Image", "Subsubsection"], 
      GeoImage[EntityValue[city, "Position"], 
       ImageSize -> Large]}, {Style["High Resolution Image", 
       "Subsubsection"], 
      GeoImage[EntityValue[city, "Position"], GeoZoomLevel -> 15, 
       ImageSize -> Large]}}, Alignment -> Left]] ], 
 Permissions -> "Public"]

I have something that sort of works. This allows the visitor to specify a city but I want to add a default for the current city for example with $GeoLocationCity.

CloudDeploy[FormFunction @@ 
  APIFunction[ "city" -> "ComputedCity", 
   TableForm[{{LatitudeLongitude[#city], 
       LocalTime[#city]}, {GeoArea[#city], 
       GeoDistance[#city, 
        GeoPosition["NullIsland"]]}, {GeoDistance[#city, 
        GeoPosition["NorthPole"]], 
       GeoDistance[#city, GeoPosition["SouthPole"]]}}, 
     TableHeadings -> {{"r1", "r2", "r3"}, {"c1", "c2"}}] & , 
   "CloudCDF"], "versa"]

I now have a simple API that lets you enter a city and see basic weather data

CloudDeploy[FormFunction @@ 
  APIFunction[ "location" -> "ComputedLocation", 
   Grid[{{Style["Current Temperature", "Subsubsection"], 
       IconData["AirTemperature", 
        WeatherData[#location, "Temperature"]], 
       QuantityForm[WeatherData[#location, "Temperature"], 
        "Abbreviation"]}, {Style["Current Relative Humidity", 
        "Subsubsection"], 
       IconData["RelativeHumidity", 
        WeatherData[#location, "Humidity"]], 
       QuantityForm[WeatherData[#location, "Humidity"], 
        "Abbreviation"]}, {Style["Current Wind Speed", 
        "Subsubsection"], 
       IconData["WindSpeed", WeatherData[#location, "WindSpeed"]], 
       QuantityForm[WeatherData[#location, "WindSpeed"], 
        "Abbreviation"]}, {Style["Current Wind Direction", 
        "Subsubsection"], 
       IconData["WindDirection", 
        WeatherData[#location, "WindDirection"]], 
       QuantityForm[WeatherData[#location, "WindDirection"], 
        "Abbreviation"]}, {Style["Geographical position", 
        "Subsubsection"], FromDMS[#location], 
       DMSString[#location, "Position"]}, {Style["Current time", 
        "Subsubsection"], 
       DateString[Now, "DateTime"]}, {Style[
        "Geographical xyz position", "Subsubsection"], 
       GeoPositionXYZ[#location]}, {Style[
        "Geographical projected position with Bonne grid projection", 
        "Subsubsection"], 
       First[GeoGridPosition[#location, "Bonne"]]}}, 
     Alignment -> Left] & , "CloudCDF"], "versa"]

How can I make an API that uses the IP address of the visitor to make a map for example?
I have tried changing my IP address with the virtual private network Private Internet Access but it still defaults to Huntington.

Yes, testing these geo-location things is not easy. In the cloud, we set

$GeoLocation

(the lat/long) using a geo-IP lookup, which is given by $RequesterAddress. Then from the location we infer

$GeoLocationCity

and

$GeoLocationCountry

Using a VPN is the right move for testing this by yourself (if you can recruit friends who are somewhere else, that helps too). You would want to confirm the address that the outside world sees, e.g. you could CloudDeploy[Delayed[$RequesterAddress]] and visit that. It’s possible that something is getting cached in the API you deployed, which you could test by calling DeleteObject.

I have the error ``CloudConnect::invbase: Invalid CloudBase CloudObject[https://www.wolframcloud.com/obj/<>StringTrim[StringJoin[UUIDUUID[]],/]]; a fully qualified domain expected. when I pressed Shift+Enter with CloudDeploy[Delayed[$RequesterAddress]]`

That looks like an error from a pre-release build of 13.2. You can work around it by passing a named object, like CloudObject[“myip.api”], as the 2nd argument to CloudDeploy:

CloudDeploy[Delayed[$RequesterAddress], CloudObject["myip.api"]]

You may also be able to fix the problem by calling Needs[“UUID`”].

Yes!!! It’s working.
Thank you!

I was able to change the map with some code:

CloudDeploy[
 Delayed[{LatitudeLongitude[$GeoLocation], 
   GeoGraphics[$GeoLocation, GeoRange -> Quantity[50, "Kilometers"], 
    ImageSize -> Large, GeoBackground -> "VectorClassic"]}], 
 CloudObject["myip.api"], Permissions -> "Public"]

Awesome, nice to see this!

Here’s a simple APIFunction that demonstrates one way to add a default for the current city:

CloudDeploy[APIFunction[{}, 
  FormFunction[{"location" -> <|"Label" -> "City", 
       "Input" -> 
        Replace[$GeoLocationCity, {e_Entity :> e["Name"], _ :> 
           "Your city here"}] , "Interpreter" -> "City"|>},
    LocalTime[#location] &, 
    AppearanceRules -> <|"Title" -> "Please enter a city"|>
    ] &
  ], CloudObject["getlocation.api"], Permissions -> "Public"]

I changed my location to Chicago with my VPN, but it still sets the default to Huntington.
If the user is in Chicago, I don’t want the default to be Huntington. I want to have a default set with the IP address and allow overriding the default with a field for a city, for example. Maybe I could use Delayed.

I would also like to option that lets the user override the default city. I’m not sure if an api can have intereaction with a user or if the right function would be FormFunction to ask the user for a city and then give data but it seems like there should be a way to add manually overriding and defaults.

There should be, and I think there could be both a programmatic way to do it and a UI way. Hannah provided a technique to collect this through a form, taking advantage of the fact that GenerateHTTPResponse can nest the active cloud object expressions like APIFunction and FormFunction within the same rest (in her case, an APIFunction that returns a FormFunction). But I think a programmatic way to do it, probably through a query parameter, is also needed and would have to have support added in to the cloud back end. I’ve put this on our to-do list of things to add to cloud.

I don’t really understand all the HTTP material, but is there is a simple way to make a form like the one Hannah created that sets the default to the current city? I think I’ll wait for a programmatic API way but is there a way for a user to open the cloud deployed webpage and automatically see their city and then if they want they can search for a different city?
I’ve tried using Delayed with Hannah’s code but I haven’t been able to make it change from Huntington to the location of the VPN server.

Your problems getting the VPN IP to be picked up are a separate independent issue – anything we talk about is going to use the GeoIP database. You can deploy this to check what IP and location is seen by cloud:

CloudDeploy[Delayed[{$RequesterAddress, $GeoLocation, $GeoLocationCountry, $GeoLocationCity}]] 

I asked the forms expert what the right way is to populate the field by default, which is to use the Input specification, like this example:

CloudPublish[
    FormFunction[{"country" -> <|"Interpreter" -> "Country", "Input" :> $GeoLocationCountry["Name"]|>}, 
        {"Country" -> #country, "Population" -> #country["Population"]} &
    ]
]

I have something that works:

CloudPublish[
 Delayed@FormFunction[{"city" -> <|"Interpreter" -> "City", 
      "Input" :> $GeoLocationCity["Name"]|>}, {"Population" -> #city[
       "Population"], "City" -> #city["Name"]} &], 
 Permissions -> "Public"]

The extra Delayed shouldn’t be necessary, if that is necessary to make it work then that is curious.

I changed the code to remove the Delayed and it worked successfully:

CloudPublish[
 FormFunction[{"city" -> <|"Interpreter" -> "City", 
     "Input" :> $GeoLocationCity["Name"]|>}, 
  Grid[{{Style[#city["Name"], "Title"]}, {Style["Current Temperature",
        "Subsubsection"], 
      IconData["AirTemperature", WeatherData[#city, "Temperature"]], 
      QuantityForm[WeatherData[#city, "Temperature"], 
       "Abbreviation"]}, {Style["Map", "Subsubsection"], 
      GeoGraphics[EntityValue[#city, "Position"], 
       GeoBackground -> "VectorClassic", ImageSize -> Large]}, {Style[
       "Image", "Subsubsection"], 
      GeoImage[EntityValue[#city, "Position"], 
       ImageSize -> Large]}, {Style["High Resolution Image", 
       "Subsubsection"], 
      GeoImage[EntityValue[#city, "Position"], GeoZoomLevel -> 15, 
       ImageSize -> Large]}}, Alignment -> Left] &, "CloudCDF"], 
 Permissions -> "Public"]

Here’s some pictures.

Is there a way to make a programmatic API with a default that could be executed with for example HTTPRequest in Mathematica that defaults to the API’s Geolocation if no parameters are specified and otherwise if a location is specified it uses that instead?

Nice!