Message Boards Message Boards

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"]
POSTED BY: Peter Burbery
27 Replies
Posted 2 years ago

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.

POSTED BY: Joel Klein
Posted 2 years ago

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`"].

POSTED BY: Joel Klein

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"]
POSTED BY: Hannah Clemens

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.enter image description hereenter image description here

POSTED BY: Peter Burbery

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

POSTED BY: Peter Burbery
Posted 2 years ago

Awesome, nice to see this!

POSTED BY: Joel Klein

I have something that works:

CloudPublish[
 Delayed@FormFunction[{"city" -> <|"Interpreter" -> "City", 
      "Input" :> $GeoLocationCity["Name"]|>}, {"Population" -> #city[
       "Population"], "City" -> #city["Name"]} &], 
 Permissions -> "Public"]
POSTED BY: Peter Burbery
Posted 2 years ago

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

POSTED BY: Joel Klein

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"]
POSTED BY: Peter Burbery

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"]
POSTED BY: Peter Burbery

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]]

POSTED BY: Peter Burbery

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"]

enter image description here enter image description here

POSTED BY: Peter Burbery

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. enter image description hereenter image description here

POSTED BY: Peter Burbery

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.

POSTED BY: Peter Burbery
Posted 2 years ago

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.

POSTED BY: Joel Klein

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.

POSTED BY: Peter Burbery
Posted 2 years ago

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"]} &
    ]
]
POSTED BY: Joel Klein

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?

POSTED BY: Peter Burbery
Posted 2 years ago

Nice!

POSTED BY: Joel Klein
Posted 2 years ago

What is "the API's geolocation"? Do you mean the geolocation where the servers run? You might think you could discover the cloud's location by making an HTTP from within the cloud to itself, but the networking there is going to use a private IP address (aka RFC 1918 address), one that doesn't have a GeoIP mapping. We do have a notion of a default geolocation for the server, but I don't think we expose it in a WL variable, it gets assigned within the web server and then passed to WL. We could do that, and there is precedence for things like that, e.g. $SystemTimeZone is the time zone the computer has before a user (or the cloud) started localizing it.

POSTED BY: Joel Klein

I deploy an API. Someone in another part of the world makes a request to my API. The server uses the requesters IP address to decide which city to compute data for. I know the server has a static IP address but if people from different parts of the world queried the Wolfram Cloud API could it tell where they were?

POSTED BY: Peter Burbery
Posted 2 years ago

The server uses the requesters IP address to decide which city to compute data for.

Yes, the geo location (lat/long), country, and city are determined in cloud by using a geo IP mapping that determines location from a public IP address. If the requester IP address is not a public address, there is no mapping, so the cloud sets these to a default location/country/city, which is that of the Wolfram company HQ in Champaign, Illinois for public cloud. For private clouds the default location is configurable.

I know the server has a static IP address but if people from different parts of the world queried the Wolfram Cloud API could it tell where they were?

The server static IP address doesn't enter in to the discussion at all. It tells where they come from using the IP and the geoIP mapping described above.

If things aren't working as expected in your testing with a VPN, you'd want to confirm that the request to the cloud is actually going through your VPN, and you'd want to see what requester IP address the cloud actually sees, and confirm that's what you expect.

POSTED BY: Joel Klein

I created a webpage that displays distances from a city to important locations. The form defaults to the current city based on the IP address. For example, when I go to the page it loads Huntington for Huntington West Virginia as the city. I can change the location to default to a different city with my VPN, which worked successfully when I tested it. I can also change the location by specifying a different city than the default such as New York City, Paris, or Brussels. I'm working on improving the form's design so it shows the city in addition to distances to interesting locations.

CloudPublish[
 FormFunction[{"city" -> <|"Interpreter" -> "City", 
     "Input" :> $GeoLocationCity["Name"]|>}, 
  Dataset[AssociationMap[
     Function[position, 
      UnitConvert[GeoDistance[#city, GeoPosition[position]], 
       "Metric"]], {"NullIsland", "NorthPole", "SouthPole", 
      "NorthGeomagneticPole", "NorthModelDipPole", 
      "SouthGeomagneticPole", "SouthModelDipPole"}], 
    DatasetTheme -> "Detailed"] &, "CloudCDF"], 
 Permissions -> "Public"]
POSTED BY: Peter Burbery

I created a simple function to retrieve weather data for a city.

CloudPublish[
 APIFunction[{"city" -> "City" :> $GeoLocationCity}, 
  DeleteMissing[
    AssociationMap[
     Function[property, 
      WeatherData[First@GeoNearest["WeatherStation", #city], 
       property]], {"CloudCoverFraction", "DewPoint", "Humidity", 
      "Pressure", "Temperature", "Visibility", "WindChill", 
      "WindDirection", "WindSpeed"}]] &], Permissions -> "Public"]

I can use the function with a default city:

URLExecute[
 HTTPRequest[CloudObject[
  "https://www.wolframcloud.com/obj/17846929-623c-4a7a-8172-\
9837ec51a19d"], <|"Query" -> {}|>]]

The function returns the following:

<|"CloudCoverFraction" -> 0, 
 "DewPoint" -> Quantity[-2.19999, "DegreesCelsius"], 
 "Humidity" -> 0.305, "Pressure" -> Quantity[1034.1, "Millibars"], 
 "Temperature" -> Quantity[15, "DegreesCelsius"], 
 "Visibility" -> Quantity[16.09, "Kilometers"], 
 "WindChill" -> Quantity[14.11, "DegreesCelsius"], 
 "WindDirection" -> Quantity[30, "AngularDegrees"], 
 "WindSpeed" -> Quantity[14.76, ("Kilometers")/("Hours")]|>

The API function also supports manual inputs. For example, the following code will return data for New York City:

URLExecute[
 HTTPRequest[
  CloudObject[
   "https://www.wolframcloud.com/obj/17846929-623c-4a7a-8172-\
9837ec51a19d"], <|"Query" -> {"city" -> "New York City"}|>]]

Here is the output:

<|"CloudCoverFraction" -> 0, 
 "DewPoint" -> Quantity[-8.29999, "DegreesCelsius"], 
 "Humidity" -> 0.248, "Pressure" -> Quantity[1035.8, "Millibars"], 
 "Temperature" -> Quantity[11.1, "DegreesCelsius"], 
 "Visibility" -> Quantity[16.09, "Kilometers"], 
 "WindChill" -> Quantity[8.93, "DegreesCelsius"], 
 "WindDirection" -> Quantity[40, "AngularDegrees"], 
 "WindSpeed" -> Quantity[18.36, ("Kilometers")/("Hours")]|>
POSTED BY: Peter Burbery

I built a page that displays information for date and time.

POSTED BY: Peter Burbery

I also made a form that displays information for a city like a city's area and median age. I also made an API function to call from Mathematica at CloudObject["https://www.wolframcloud.com/obj/73c731cb-7afb-4b8b-ad06-509c3aab3dd1"]

POSTED BY: Peter Burbery

I created a cloud form that takes too long to evaluate. Does the cloud server abort after a certain amount of time if the computation isn't finished?

POSTED BY: Peter Burbery
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract