Skip to content

Chapter 7: API Data Fetching#

Welcome back! In Chapter 5: Main App Logic, we saw how our MainView component acts as the central conductor, managing the application's state and coordinating different parts. In Chapter 6: Data Models, we learned about the blueprints (interfaces) that define the structure of the data used throughout our project, like metrics or gallery image details.

But where does some of this crucial data actually come from? We handle user file uploads (Chapter 2: User Input Handling (Files & Interaction)), but information about buildings, a list of available photos in a gallery, or details about a specific photo often doesn't live directly in our application or on the user's device. This data often resides on external servers.

What's the Problem?#

Imagine you've uploaded a photo, and our application has extracted the exact GPS coordinates where it was taken. To show you the 3D model of the building in that location (Chapter 3: 3D Geographic Visualization) and display its details (Chapter 4: Building Information Display), our app needs to know things like:

  • What are the precise geographic boundaries (GeoJSON) of the building at those coordinates?
  • What are its known properties, like height?

Similarly, if you want to see a gallery of all photos related to buildings, our app needs a list of those photos, their locations, and maybe preview images – data that is managed centrally on a server.

Our application can't just magically know this information. It needs a way to ask another computer (a server) for it.

What is API Data Fetching?#

API Data Fetching is the process of sending requests from our application (running in your browser) to a server somewhere else on the internet to retrieve data.

Think of it like this:

  • Your Application (Client): You, sitting at a table in a restaurant, ready to order.
  • The Server (API): The kitchen and the menu at the restaurant. It knows how to prepare different dishes (provide different types of data).
  • The Request: You telling the waiter what you want to order ("I'd like the building data for coordinates X, Y, Z" or "Show me the list of all photos").
  • The Response: The waiter bringing your food (the data) back from the kitchen.

An API (Application Programming Interface) is essentially the "menu" and the set of rules the server provides, telling you what kinds of requests you can make and what kind of responses you'll get back.

In web applications, these requests are typically made using the HTTP protocol, the same one your browser uses to load web pages. We send requests to specific URLs (addresses) provided by the server's API. The server then sends back data, often formatted as JSON (JavaScript Object Notation), which is easy for our JavaScript code to understand and use.

Our project uses standard web technologies to perform API data fetching.

How it Works in This Project: Using fetch#

In modern web development, the built-in way to make HTTP requests from JavaScript is the fetch API. It's like the standard tool for asking the server for data.

Let's look at two examples from our project: fetching building information and fetching gallery information.

Example 1: Fetching Building Information (src/api/fetch-building.ts)#

This file contains the fetchBuilding function, which is responsible for querying an external service to get details about a building near a given location and direction.

// src/api/fetch-building.ts (Simplified)
const API_URL = "https://api.buildingshistory.co.uk"; // The server's address

export const fetchBuilding = async (
  lat: string,
  lon: string,
  camAltitude: string, // Not directly used in the request URL snippet shown
  camDirection: string
) => {
  // Prepare the URL with query parameters (lat, lon, direction)
  const url = `${API_URL}/api/v1/building-part/nearest?latitude=${parseFloat(lat)}&longitude=${parseFloat(lon)}&imagedirection=${camDirection}`;

  try {
    // Use the fetch API to send a GET request to the server
    const response = await fetch(url);

    // Check if the request was successful (e.g., HTTP status 200-299)
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    // Parse the JSON data from the response body
    const data = await response.json();

    // Check if the response contains the expected building data
    if (
      data?.data?.building_part?.length > 0 &&
      data.data.building_part[0]?.geojson
    ) {
      // If data found, return a structured object (matching a simplified Data Model)
      return {
        geojson: data.data.building_part[0].geojson, // The building's GeoJSON
        cameraGPSData: [ // Camera info derived from input
          {
            coordinates: [parseFloat(lon), parseFloat(lat), parseFloat(camAltitude)],
            bearing: parseFloat(camDirection),
            altitude: parseFloat(camAltitude),
          },
        ],
      };
    }

    // If no building data found in the response
    return null;

  } catch (error) {
    // Handle any errors during the fetch process (network issues, server errors)
    console.error("Failed to fetch building:", error);
    // Maybe re-throw or return a specific error indicator
    return null; // Return null or throw new Error("Fetch failed") for handling elsewhere
  }
};

Let's break down this simplified code:

  1. const API_URL = "...";: This is the base address of the API server we want to talk to.
  2. async (lat, lon, ...): This is an async function, which makes working with fetch (which is "asynchronous", meaning it takes time to complete and doesn't block the rest of your code) much easier using await.
  3. const url = \...`;: We build the specific URL for this request. Notice how we include thelat,lon, andcamDirectionvalues as "query parameters" (the stuff after the?in the URL). This is how we send information *to* the server in a GET request. We also parse the string inputs (lat,lon) into numbers usingparseFloat` before putting them in the URL.
  4. const response = await fetch(url);: This is the core fetch call! It sends a GET request to the constructed URL and waits for the server's response.
  5. if (!response.ok) { ... }: After getting a response, we check if it was successful (response.ok is true for HTTP status codes 200-299). If not, we throw an error.
  6. const data = await response.json();: The server sends the data in the body of the response, usually as JSON text. response.json() reads that text and parses it into a JavaScript object. await is used because reading and parsing the body also takes time.
  7. if (data?.data?.building_part?...) { ... }: We check if the received data object has the structure we expect (Chapter 6: Data Models) and contains the building's GeoJSON. The ?. is "optional chaining", a safe way to access nested properties that might be missing without causing errors.
  8. return { geojson: ..., cameraGPSData: ... };: If we found the data, we create a structured object (matching our internal data model) and return it.
  9. return null;: If no building was found or the response structure was unexpected, we return null.
  10. try { ... } catch (error) { ... }: This block handles potential errors, like the server being down or a network problem. We log the error to the console.

This fetchBuilding function encapsulates the entire process of asking the server for building data.

This file contains functions for interacting with a different API endpoint to manage photos in a gallery, including fetching lists of photos.

// src/api/fetch-gallery.ts (Simplified)
let endpoint = "https://pic2bim.co.uk/"; // Another server address

export const get_unassigned_photos = async (user_id: number, bearerToken: string) => {
  console.log("Fetching unassigned photos..."); // Log for debugging

  try {
    const response = await fetch(
      `${endpoint}comm_unassigned?user_id=${user_id}`, // URL with user ID
      {
        method: "POST", // This request uses the POST method
        headers: {
          "Content-Type": "application/json", // Tell the server we're sending JSON
          "Authorization": `Bearer ${bearerToken}`, // Send the user's login token
        },
        // For POST, you might also send a request body, but this endpoint doesn't show one in the snippet.
      }
    );

    let res: any = await response.json(); // Parse the JSON response

    if (!response.ok) {
      throw new Error(`Network response was not ok, status: ${response.status}`);
    }

    // Check the structure of the response data (referencing Data Models)
    if (res?.photos_ids) {
      return res.photos_ids; // Return the array of photo IDs
    } else {
      return []; // Return an empty array if no IDs found
    }

  } catch (error) {
    console.error("Failed to fetch unassigned photos:", error);
    return []; // Return empty array on error
  }
};

export const get_photo = async (photo_id: number, bearerToken: string) => {
   console.log(`Fetching photo details for ID: ${photo_id}`); // Log

  try {
    const response = await fetch(
      `${endpoint}comm_get_photo?photo_id=${photo_id}`, // URL with photo ID
      {
        method: "POST", // This request also uses POST
        headers: {
          "Content-Type": "application/json",
          "Authorization": `Bearer ${bearerToken}`, // Requires authentication
        },
      }
    );

    let res: any = await response.json(); // Parse JSON response

    if (!response.ok) {
      throw new Error(`Network response was not ok, status: ${response.status}`);
    }

    // Check response structure (referencing Data Models)
    if (res?.photo) {
      // res.photo should match the GalleryImage structure simplified in Chapter 6
      return res.photo; // Return the photo details object
    } else {
      return null; // Return null if photo not found
    }

  } catch (error) {
    console.error(`Failed to fetch photo ${photo_id}:`, error);
    return null; // Return null on error
  }
};

Key points in these gallery fetching functions:

  1. Different Endpoint: They talk to a different endpoint URL (https://pic2bim.co.uk/). An application often interacts with multiple APIs.
  2. method: "POST": Unlike fetchBuilding which uses the default GET method (good for just retrieving data based on URL parameters), these functions explicitly use the POST method. POST is often used when sending more complex data to the server or when the request modifies something on the server (though here it's used for retrieval, possibly for historical reasons or specific API design).
  3. headers: We include headers in the fetch options object.
    • "Content-Type": "application/json": This tells the server that if we were sending a request body (which isn't shown in these simplified snippets, but is common with POST), it would be in JSON format.
    • "Authorization": \Bearer ${bearerToken}`: This is very important! Many APIs require authentication. After a user logs in ([Chapter 1: Application Views](01_application_views_.md)),MainViewstores abearerToken([Chapter 5: Main App Logic](05_main_app_logic_.md)). We include this token in theAuthorization` header of subsequent requests. This tells the server who is making the request and confirms they are allowed to access the data. If the token is missing or invalid, the server will likely return an error (like status 401 Unauthorized).
  4. Parsing Response: Again, await response.json() is used to get the data from the server's response.
  5. Returning Data: The functions return the relevant part of the response data (res.photos_ids or res.photo) or an empty array/null on failure, matching the expected structure defined in our Data Models (Chapter 6: Data Models).

These functions demonstrate how our application makes specific requests to different API servers to get the data it needs for various features like displaying a single building or a photo gallery.

Connecting Fetching to Main App Logic#

As discussed in Chapter 5: Main App Logic, the MainView component is the conductor that calls these API fetching functions.

When the handleImage function in MainView processes an image and extracts GPS coordinates, it calls a function like getPolygon (which in turn calls fetchBuilding). When the user navigates to the Showcase view, MainView (or the MapShowcaseView component it renders) might call get_unassigned_photos to get the list of image IDs to display.

Here's a simplified flow showing API fetching triggered by image processing:

sequenceDiagram
    participant User
    participant PhotoView
    participant MainView
    participant API Fetching Function (e.g., fetchBuilding)
    participant External Server

    User->PhotoView: Uploads image
    PhotoView->MainView: Calls onImageChange prop (setSelectedImg)
    MainView->MainView: Updates selectedImg state
    MainView->MainView: useEffect triggers image processing (handleImage)
    MainView->MainView: handleImage extracts GPS data, calls getPolygon
    MainView->API Fetching Function (fetchBuilding): Calls fetchBuilding(lat, lon, ...)
    API Fetching Function (fetchBuilding)->External Server: Sends HTTP Request (GET URL)
    External Server->External Server: Looks up building data
    External Server-->API Fetching Function (fetchBuilding): Sends HTTP Response (JSON data)
    API Fetching Function (fetchBuilding)->API Fetching Function (fetchBuilding): Parses JSON, checks data
    API Fetching Function (fetchBuilding)-->MainView: Returns structured building data (or null)
    MainView->MainView: Updates geo state, changes activeLayout to RESULT
    MainView->MapResultView: Renders MapResultView with geo data
    MapResultView-->>User: Displays 3D building on map

This sequence highlights how MainView initiates the API call via the dedicated fetching function, the fetching function communicates with the external server, and the result comes back to MainView to update the application's state and trigger view changes and rendering.

Error Handling#

Network requests are inherently unreliable – the server might be down, there might be internet issues, or the server might return an error indicating that the request was bad (e.g., wrong parameters) or unauthorized (missing token).

Both fetchBuilding and the gallery functions use try...catch blocks. The try block contains the code that might fail (the fetch call and processing the response). If an error occurs at any point within the try block, the code inside the catch(error) block is executed.

The catch block typically logs the error (console.error) so developers can see what went wrong. In a production application, you might do more sophisticated error handling, like showing an error message to the user (Chapter 5: Main App Logic manages an errMsg state potentially for this) or retrying the request. For this project, logging and returning null or an empty array on error allows the calling code (MainView) to check the result and handle the lack of data gracefully (e.g., show a "No data found" message).

Checking response.ok is also a basic but important form of error handling at the HTTP level, distinguishing between a successful request (even if the data inside isn't what we hoped for, like no building found) and a failed request (like a server error).

Conclusion#

In this chapter, we learned about API Data Fetching, the process by which our application communicates with external servers to retrieve necessary information like building details and photo gallery data. We saw how modern JavaScript uses the fetch API to send HTTP requests (like GET and POST) to specific server URLs, how to include important details like query parameters and authentication tokens in headers, and how to receive and parse the JSON responses using await response.json(). We also touched upon basic error handling with try...catch.

These fetching functions act as dedicated modules (src/api/fetch-building.ts, src/api/fetch-gallery.ts), providing a clean way for our MainView component (Chapter 5) to get external data and update the application state accordingly.

Now that we know how to get data from APIs and user input, the next chapter will focus on the crucial step of Geographic Data Processing – taking that raw or fetched data and transforming it into the formats and calculations our application needs for visualization and display.

Next Chapter: Geographic Data Processing


Generated by AI Codebase Knowledge Builder. References: 1(https://github.com/buildvoc/mapbox-gl_deck.gl_turf.js-ts/blob/3d8a4a53d878db3324af6466e0f99e5fb072bbe7/src/api/fetch-building.ts), 2(https://github.com/buildvoc/mapbox-gl_deck.gl_turf.js-ts/blob/3d8a4a53d878db3324af6466e0f99e5fb072bbe7/src/api/fetch-gallery.ts)