StudentLifeMap / drawMap.cpp
drawMap.cpp
Raw
#include "mapTools.h"
#include "drawMap.h"

#include <stdbool.h>
#include <string>
#include <cstring>
#include <cmath>
#include <iostream>
#include <sstream>

#include <algorithm>
#include <point.hpp>
#include <rectangle.hpp>
#include <graphics.hpp>
#include <canvas.hpp>

// initialize all GLOBAL variables
int initial_zoom = 0;
int prev_zoom;
int roadWidth = 0;
int mainRoadWidth = 2;
int highwayWidth = 5;
int zoom_level = 0;

// boolean variables for button toggle 
bool lightMode = true;
bool showSubways = false;
bool showCafes = false;
bool showSchoolLife = false;
bool showFood = false;
bool dropDown = true;
bool intersectToggle = false;
bool GO = false;

ezgl::point2d currIntersect;
std::string newMap;

std::pair<int, int> road;
std::pair<int, int> mainRoad;
std::pair<int, int> highwayRoad;

// global variables for finding intersections for start positions and destinations
StreetIdx street1OfIntersection1;
StreetIdx street1OfIntersection2;
IntersectionIdx startIntersection =-1;
IntersectionIdx destinationIntersection = -1;
std::vector <IntersectionIdx> intersectionOfStreets1;
std::vector <IntersectionIdx> intersectionOfStreets2;
std::vector<ezgl::point2d> allFoods;
std::vector<StreetSegmentIdx> segPath;

std::vector<int> roadColor(3);
std::vector<int> mainRoadColor(3);
std::vector<int> highwayRoadColor(3);

//m3 global variables
bool twoIntersectClick = false; 
std::pair<IntersectionIdx, IntersectionIdx> twoIntersections = std::make_pair(-1, -1);

// unordered map: holds the city strings and file name for loading new map
// without recompiling
std::unordered_map<std::string, std::string> places ({
 {"Beijing, China", "/cad2/ece297s/public/maps/beijing_china.streets.bin"},
 {"Cairo, Egypt", "/cad2/ece297s/public/maps/cairo_egypt.streets.bin"}, 
 {"Cape Town, South Africa", "/cad2/ece297s/public/maps/cape-town_south-africa.streets.bin"},
 {"Golden Horseshoe, Canada", "/cad2/ece297s/public/maps/golden-horseshoe_canada.streets.bin"},
 {"Hamilton, Canada", "/cad2/ece297s/public/maps/hamilton_canada.streets.bin"},
 {"Hong Kong, China", "/cad2/ece297s/public/maps/hong-kong_china.streets.bin"},
 {"Iceland", "/cad2/ece297s/public/maps/iceland.streets.bin"},
 {"Interlaken, Switzerland", "/cad2/ece297s/public/maps/interlaken_switzerland.streets.bin"},
 {"London, England", "/cad2/ece297s/public/maps/london_england.streets.bin"},
 {"Moscow, Russia", "/cad2/ece297s/public/maps/moscow_russia.streets.bin"},
 {"New Delhi, India", "/cad2/ece297s/public/maps/new-delhi_india.streets.bin"},
 {"New York, USA", "/cad2/ece297s/public/maps/new-york_usa.streets.bin"},
 {"Rio de Janeiro, Brazil", "/cad2/ece297s/public/maps/rio-de-janeiro_brazil.streets.bin"},
 {"Saint Helena", "/cad2/ece297s/public/maps/saint-helena.streets.bin"},
 {"Singapore", "/cad2/ece297s/public/maps/singapore.streets.bin"},
 {"Sydney, Australia", "/cad2/ece297s/public/maps/sydney_australia.streets.bin"},
 {"Tehran, Iran", "/cad2/ece297s/public/maps/tehran_iran.streets.bin"},
 {"Tokyo, Japan", "/cad2/ece297s/public/maps/tokyo_japan.streets.bin"},
 {"Toronto, Canada", "/cad2/ece297s/public/maps/toronto_canada.streets.bin"}});

// function: draw_main_canvas
// this function is called everytime the map is reloaded to redraw the main canvas
void draw_main_canvas(ezgl::renderer *g)
{
    // draw the base rectangle and fill it with the colour of the base map
    // (light/dark mode) based on time or toggle button
    g->draw_rectangle({x_from_lon(min_lon), y_from_lat(min_lat)}, {x_from_lon(max_lon), y_from_lat(max_lat)});
    lightMode ? g->set_color(245, 235, 224) : g->set_color(58, 72, 91);
    g->fill_rectangle({x_from_lon(min_lon), y_from_lat(min_lat)}, {x_from_lon(max_lon), y_from_lat(max_lat)});

    draw_features(g);       // draw all features
    draw_road(g);           // draw all roads
    showStreetNames(g);     // show all street names

    // here, check if buttons are toggled and display 
    // values and points accordingly

    // show subways if button is toggled
    if (showSubways) show_subways(g);

    // show cafes if button is toggled
    if (showCafes) show_cafes(g, "cafe"); 

    // show schools, libraries and univerities if button is toggled
    if (showSchoolLife) {
        show_cafes(g, "library");
        show_cafes(g, "school");
        show_cafes(g, "university");
    }

    // show fast foods if button is toggled
    if (showFood) show_food(g);
    
    // intersection toggle
    if (intersectToggle)
        intersect_toggle(g, currIntersect);

    if(twoIntersectClick) {
        draw_intpath(g);
        showPathNames(g);
    }

    // GO button toggling
    if(GO){
        draw_intpath(g);
        showPathNames(g);
        draw_orang_flag(g);
    }

    // label all intersection
    label_intersections(g); 
}

// function: drawMap
// this function sets up and draws the map
void drawMap(){
    // settings and identifiers
    ezgl::application::settings settings;
    settings.main_ui_resource = "libstreetmap/resources/main.ui";
    settings.window_identifier = "MainWindow";
    settings.canvas_identifier = "MainCanvas";

    ezgl::application application(settings);

    // this checks the current time and displays light/dark mode accordingly
    int hour = getHour();
    (hour < 5 || hour > 18) ? lightMode = false : lightMode = true;

    // create initial world, add canvas and run the application
    ezgl::rectangle initial_world({x_from_lon(min_lon), y_from_lat(min_lat)}, {x_from_lon(max_lon), y_from_lat(max_lat)});

    initial_zoom = initial_world.width();
    application.add_canvas("MainCanvas", draw_main_canvas, initial_world);
    application.run(initial_setup, act_on_mouse_press, nullptr, nullptr);
}

// function: draw_features
// this function draws all the features on a map like rivers, streams, greenspace,
// buildings, and bodies of water
void draw_features(ezgl::renderer *g) {
    ezgl::rectangle visible = g->get_visible_world(); // initialize visible varible to adjust zoom

    // loop through the total number of features and displays them in layers on the canvas
    for (int curr_id = 0; curr_id < getNumFeatures(); curr_id++)
    {
        // initialize variables to iterate through the features, check each feature and its # of vertices
        int currentFeature = sortedFeaturesArea[curr_id].first;
        int vertices = getNumFeaturePoints(currentFeature);

       // check zoom position, don't display features outside of current position
        if (featureMax[currentFeature] < visible.bottom_left().y  || featureMin[currentFeature] > visible.top_left().y)
            continue;
        
        // all the if, else if, else statements check the type of feature and sets the colours accordingly
        // light mode variable toggles colours to change from dark/light mode
        if (getFeatureType(currentFeature) == PARK || getFeatureType(currentFeature) == GOLFCOURSE) lightMode ? g->set_color(183, 228, 199) : g->set_color(37, 91, 91); // lighter green
        else if (getFeatureType(currentFeature) == GREENSPACE) lightMode ? g->set_color(116, 198, 157) : g->set_color(52, 96, 89); // darker green
        else if (getFeatureType(currentFeature) == LAKE) g->set_color(137, 194, 217); // dark blue
        else if (getFeatureType(currentFeature) == RIVER || getFeatureType(currentFeature) == STREAM){
            if (getFeatureType(currentFeature) == STREAM && visible.width() > 50000) continue;
            g->set_color(97, 165, 194);
        }
        else if (getFeatureType(currentFeature) == GLACIER) g->set_color(ezgl::WHITE); 
        else if (getFeatureType(currentFeature) == BEACH) lightMode ? g->set_color(254, 209, 140) : g->set_color(87, 97, 83);
        else if (getFeatureType(currentFeature) == BUILDING) {
            int currZoom = visible.width();
            if (currZoom > 4000) continue;
            lightMode ? g->set_color(214, 204, 194) : g->set_color(79, 101, 131);
        }
        else lightMode ? g->set_color(245, 235, 224) : g->set_color(58, 72, 91);

        // here using the colours identified, fill the polygon or draw a line depending on whether is is an open or closed area feature
        if (vertices <= 1) continue;
        else {
            // if there is a feature that is closed (area > 0)
            if (sortedFeaturesArea[curr_id].second > 0) {
                // variable to hold the points of feature
                std::vector<ezgl::point2d> pointsofFeature;

                // get all the verticies for a feature to fill in with colours
                for (int curr = 0; curr < vertices; ++curr)
                    pointsofFeature.push_back(latlon_to_point2d(getFeaturePoint(curr, currentFeature)));

                // fill in the features with chosen colours
                g->fill_poly(pointsofFeature);
            }
            // if the feature area is open
            else
                for (int curr = 0; curr < vertices - 1; ++curr)
                    g->draw_line(latlon_to_point2d(getFeaturePoint(curr, currentFeature)), latlon_to_point2d(getFeaturePoint(curr + 1, currentFeature)));
        }
    }
}

// function: draw_roads
// this function draws all the roads in layers starting with residential roads,
// main roads and lastly highways/motorways
void draw_road(ezgl::renderer *g) {
    // get visible world and curr zoom width
    ezgl::rectangle visible = g->get_visible_world();
    int curr_zoom = visible.width();

    // set the line widths for the different roads
    setLineWidths(roadWidth, mainRoadWidth, highwayWidth, roadColor, mainRoadColor, highwayRoadColor, curr_zoom, lightMode);

    // set line widths and colours for residential roads
    g -> set_line_width(roadWidth);
    g -> set_color(roadColor[0], roadColor[1], roadColor[2]);
    
    // draw each residential street, segment by segment
    for (unsigned i = 0; i < osm_residential.size(); i++)
    {
        // Get residential road and unknown members from global variable
        std::vector<OSMID> residential_members = getWayMembers(osm_residential[i]);

        // initialize iterators to draw the roads
        LatLon start = findLatLonOfOSMNode(residential_members[0]);
        LatLon next;

        // this look checks for number of curves in a street segment and iterates through it to draw curved streets
        for (unsigned j = 0; j < residential_members.size() - 1; j++) {
            // checks for last member of vector
            if (j == residential_members.size() - 1) 
                next = findLatLonOfOSMNode(residential_members[residential_members.size() - 1]);
            else // j = numOfCurves, no more curves left, next is the end
                next = findLatLonOfOSMNode(residential_members[j + 1]);

            // check to see if visible and plot accordingly (checking zoom level)
            if ((visible.width() > 10000) || ((latlon_to_point2d(start).x < visible.bottom_left().x || latlon_to_point2d(start).x > visible.top_right().x) && (latlon_to_point2d(next).x < visible.bottom_left().x || latlon_to_point2d(next).x > visible.top_right().x)))
                continue;
            else g->draw_line(latlon_to_point2d(start), latlon_to_point2d(next));

            // next will become the start of next segment in street segment
            start = next;
        }
    }

    // set line width and colours for main roads
    g->set_line_width(mainRoadWidth);
    g->set_color(mainRoadColor[0], mainRoadColor[1], mainRoadColor[2]);

    // draw each main road, segment by segment
    for (unsigned i = 0; i < osm_main_roads.size(); i++) {
        // Get main road members from global variable
        std::vector<OSMID> main_road_members = getWayMembers(osm_main_roads[i]);

        // initialize iterators to draw the roads
        LatLon start = findLatLonOfOSMNode(main_road_members[0]);
        LatLon next;

        // this look checks for number of curves in a street segment and iterates through it to draw curved streets
        for (unsigned j = 0; j < main_road_members.size() - 1; j++){
            // check for last member of main roads vector
            if (j == main_road_members.size() - 1)
                next = findLatLonOfOSMNode(main_road_members[main_road_members.size() - 1]);
            else // j = numOfCurves, no more curves left, next is the end
                next = findLatLonOfOSMNode(main_road_members[j + 1]);

            // check to see if visible and plot accordingly (checking zoom level)
            if ((visible.width() > 30000) || ((latlon_to_point2d(start).x < visible.bottom_left().x || latlon_to_point2d(start).x > visible.top_right().x) && (latlon_to_point2d(next).x < visible.bottom_left().x || latlon_to_point2d(next).x > visible.top_right().x)))
                continue;
            else g->draw_line(latlon_to_point2d(start), latlon_to_point2d(next));

            // next will become the start of next segment in street segment
            start = next;
        }
    }

    g->set_line_width(highwayWidth);
    g->set_color(highwayRoadColor[0], highwayRoadColor[1], highwayRoadColor[2]);

    // draw each highway/motorway
    for (unsigned i = 0; i < osm_highways.size(); i++) {
        // Get highway members from global variable
        std::vector<OSMID> highway_members = getWayMembers(osm_highways[i]);

        // initialize iterators to draw the roads
        LatLon start = findLatLonOfOSMNode(highway_members[0]);
        
        LatLon next;

        // this look checks for number of curves in a street segment and iterates through it to draw curved streets
        for (unsigned j = 0; j < highway_members.size() - 1; j++){
            // check for last member of highway vector
            if (j == highway_members.size() - 1)
                next = findLatLonOfOSMNode(highway_members[highway_members.size() - 1]);
            else // j = numOfCurves, no more curves left, next is the end
                next = findLatLonOfOSMNode(highway_members[j + 1]);

            // check to see if visible and plot accordingly (checking zoom level)
            if ((latlon_to_point2d(start).x < visible.bottom_left().x || latlon_to_point2d(start).x > visible.top_right().x) && (latlon_to_point2d(next).x < visible.bottom_left().x || latlon_to_point2d(next).x > visible.top_right().x))
                continue;
            else g->draw_line(latlon_to_point2d(start), latlon_to_point2d(next));

            // next will become the start of next segment in street segment
            start = next;
        }
    }
}

// function showStreetNames
// this function shows all the street names according to the zoom
void showStreetNames(ezgl::renderer *g) {
    // get visible world and set the font size
    ezgl::rectangle visible = g->get_visible_world();
    g->set_font_size(std::max(visible.width() / 10000, 10.0));

    // this loop iterates through all the street segments and prints the street name according to the set bounds
    for (int streetSeg = 0; streetSeg < getNumStreetSegments(); streetSeg++) {
        // initialize variables/ objects to get the information needed
        StreetSegmentInfo streetSegInfo = getStreetSegmentInfo(streetSeg);
        StreetIdx streetID = streetSegInfo.streetID;

        // set a start and end point for each street segment
        ezgl::point2d start = intersectionPoint2d[streetSegInfo.from];
        ezgl::point2d end = intersectionPoint2d[streetSegInfo.to];

        // typecast the midpoint of the point2d start and end points into floats and create a new point
        float x = (start.x + end.x) / 2;
        float y = (start.y + end.y) / 2;

        ezgl::point2d streetNamePosition = ezgl::point2d(x, y);

        // check zoom level before displaying the street names
        // check one way streets and display arrows if so
        if (visible.contains(streetNamePosition)!= true) continue; 
        else {
            if (visible.width() < 5000 && getStreetName(streetID) != "<unknown>" && streetSegInfo.numCurvePoints < 4 && streetSegInfo.numCurvePoints != 2) { 
                lightMode? g -> set_color(ezgl::BLACK): g -> set_color(245,235,224);
                
                // rotate text so that it aligns with the streets, if 90 degrees(edge case)
                if (start.y == end.y)
                    g->set_text_rotation(90);
                else {
                    double angle = atan((end.y - start.y) / (end.x - start.x)) / kDegreeToRadian;
                    g->set_text_rotation(angle);
                }

                // check for one way streets
                if (streetSegInfo.oneWay) {
                    if(start.x > end.x || ((start.x == end.x) && (start.y <= end.y))) {
                        std::string name = "" + getStreetName(streetID) + "";
                        g -> draw_text(streetNamePosition, name, findStreetSegmentLength(streetSeg) + 9, 10.0);
                    }
                    else{
                        std::string name = "" + getStreetName(streetID) + "";
                        g -> draw_text(streetNamePosition, name, findStreetSegmentLength(streetSeg) + 9, 10.0);
                    }
                } 
                else g -> draw_text(streetNamePosition, getStreetName(streetID), findStreetSegmentLength(streetSeg), 10.0);
            } 
            else if (visible.width() < 1000 && streetSegInfo.oneWay && streetSegInfo.numCurvePoints < 6 && streetSegInfo.numCurvePoints != 2){
                lightMode? g -> set_color(ezgl::BLACK): g -> set_color(245,235,224);
                if (start.y == end.y) g -> set_text_rotation(90); 
                else{
                    double angle = atan((end.y - start.y)/(end.x - start.x)) / kDegreeToRadian; 
                    g -> set_text_rotation(angle); 
                }

                if(start.x > end.x || ((start.x == end.x) && (start.y <= end.y))) {
                    std::string name = "";
                    g -> draw_text(streetNamePosition, name);
                } 
                else {
                    std::string name = "";
                    g -> draw_text(streetNamePosition, name);
                }
            }
        }
    }
}

void showPathNames(ezgl::renderer *g) {
    // get visible world and set the font size
    ezgl::rectangle visible = g->get_visible_world();
    g->set_font_size(std::max(visible.width() / 10000, 10.0));
    g->format_font("Noto Sans CJK", ezgl::font_slant::normal, ezgl::font_weight::bold);

    // this loop iterates through all the street segments and prints the street name according to the set bounds
    for (auto streetSeg : segPath) {
        // initialize variables/ objects to get the information needed
        StreetSegmentInfo streetSegInfo = getStreetSegmentInfo(streetSeg);
        StreetIdx streetID = streetSegInfo.streetID;

        // set a start and end point for each street segment
        ezgl::point2d start = intersectionPoint2d[streetSegInfo.from];
        ezgl::point2d end = intersectionPoint2d[streetSegInfo.to];

        // typecast the midpoint of the point2d start and end points into floats and create a new point
        float x = (start.x + end.x) / 2;
        float y = (start.y + end.y) / 2;

        ezgl::point2d streetNamePosition = ezgl::point2d(x, y);

        // check zoom level before displaying the street names
        // check one way streets and display arrows if so
        if (visible.contains(streetNamePosition)!= true) continue; 
        else {
            if (visible.width() < 5000 && getStreetName(streetID) != "<unknown>" && streetSegInfo.numCurvePoints < 4 && streetSegInfo.numCurvePoints != 2) { 
                lightMode? g -> set_color(ezgl::BLACK): g -> set_color(245,235,224);
                
                // rotate text so that it aligns with the streets, if 90 degrees(edge case)
                if (start.y == end.y)
                    g->set_text_rotation(90);
                else {
                    double angle = atan((end.y - start.y) / (end.x - start.x)) / kDegreeToRadian;
                    g->set_text_rotation(angle);
                }

                // check for one way streets
                if (streetSegInfo.oneWay) {
                    if(start.x > end.x || ((start.x == end.x) && (start.y <= end.y))) {
                        std::string name = "" + getStreetName(streetID) + "";
                        g -> draw_text(streetNamePosition, name, findStreetSegmentLength(streetSeg) + 9, 10.0);
                    }
                    else {
                        std::string name = "" + getStreetName(streetID) + "";
                        g -> draw_text(streetNamePosition, name, findStreetSegmentLength(streetSeg) + 9, 10.0);
                    }
                } 
                else g -> draw_text(streetNamePosition, getStreetName(streetID), findStreetSegmentLength(streetSeg), 10.0);
            } 
            else if (visible.width() < 1000 && streetSegInfo.oneWay && streetSegInfo.numCurvePoints < 6 && streetSegInfo.numCurvePoints != 2){
                lightMode? g -> set_color(ezgl::BLACK): g -> set_color(245,235,224);
                if (start.y == end.y) g -> set_text_rotation(90); 
                else{
                    double angle = atan((end.y - start.y)/(end.x - start.x)) / kDegreeToRadian; 
                    g -> set_text_rotation(angle); 
                }

                if(start.x > end.x || ((start.x == end.x) && (start.y <= end.y))) {
                    std::string name = "";
                    g -> draw_text(streetNamePosition, name);
                } 
                else{
                    std::string name = "";
                    g -> draw_text(streetNamePosition, name);
                }
            }
        }
    }
    // font formatting for display
    g->format_font("Noto Sans CJK", ezgl::font_slant::normal, ezgl::font_weight::normal);
}



// function: show_subways
// shows all the subway stations that are in a city
void show_subways(ezgl::renderer *g) {
    if (subwayStationsxy.size() == 0) return;
    // set colour of subways stations according to colour mode
    lightMode? g -> set_color(247, 55, 114): g -> set_color(255, 155, 185);
    ezgl::rectangle visible = g -> get_visible_world(); 
    ezgl::point2d start, next;

    // using information from the global variable that stores location of subway stations, print them
    for (unsigned i = 0; i < subwayStationsxy.size() - 1; i++) {
        // intiialize start and next iterators to draw the subways
        start = subwayStationsxy[i];
        next = subwayStationsxy[i + 1];

        // find distance between points to check for different subway lines
        double dis = findDistanceBetweenTwoPoints(std::make_pair(point2d_to_latlon(start), point2d_to_latlon(next)));

        // if the map being loaded is not tokyo or new york, then draw the subways as lines with subway
        // stations as dots
        if ((dis < 4500 && newMap != "Tokyo, Japan") && (dis < 4500 && newMap != "New York, USA")){
            if (i < subwayStationsxy.size() - 1)
                g -> draw_line(start, next);
            else {
                g -> draw_line(start, next);
                g -> fill_arc(next, visible.width()/400, 0, 360);
            }
        }

        // populate subway stations with a pink dot
        g -> fill_arc(start, visible.width()/200, 0, 360);

        // current subway station becomes the next subway station to iterate through
        start = next;
    }
}

// function: show_cafes
// shows all the cafes/ coffee shops that are in a city
void show_cafes(ezgl::renderer *g, std::string amenity) {
    // set colour and get visible world and set all zoom variables
    g->set_color(0, 112, 74);
    g->set_text_rotation(0);
    
    // get and set all visible screem variables to check for clustering
    ezgl::rectangle visible = g->get_visible_world();
    double screenWidth = visible.width();
    double screenHeight = visible.height();
    ezgl::point2d origin = visible.bottom_left();
    int gridParameter;

    // determine the number of rectangles per height and per length of grid
    if (screenWidth < 2000) gridParameter = 1;
    else if (screenWidth < 4000) gridParameter = 5;
    else if (screenWidth < 6000) gridParameter = 5;
    else if (screenWidth < 10000) gridParameter = 4;
    else if (screenWidth < 15000) gridParameter = 4;
    else if (screenWidth < 25000) gridParameter = 3;
    else if (screenWidth < 42000) gridParameter = 3;
    else gridParameter = 2;

    std::vector<std::vector<ezgl::point2d>> grid(gridParameter * gridParameter);
    std::vector<ezgl::rectangle> rectangleVec;

    //determine the rectangle height and length
    double gridboxWidth = (double)screenWidth / (double)gridParameter; 
    double gridboxHeight = (double)screenHeight / (double)gridParameter;

    //iterate over the x and y coordinates for the origin of each grid rectangle
    for (int i = 0; i < gridParameter; i++) {
        for (int j = 0; j < gridParameter; j++) {
            ezgl::point2d recOrigin = ezgl::point2d(origin.x + (i * gridboxWidth), origin.y + (j * gridboxHeight));
            ezgl::rectangle gridRec = ezgl::rectangle(recOrigin, gridboxWidth, gridboxHeight);
            
            // append each rectangle in grid to array
            rectangleVec.push_back(gridRec);
        }
    }

    //initialize counter for POIs of current type
    int totalPOIS = 0; 
    
    //std::cout << getNumPointsOfInterest() << "\n";
    // iterates through the POIs
    for (int currIdx = 0; currIdx < getNumPointsOfInterest(); currIdx++) {
        //change POI latlon to 2d
        ezgl::point2d poiPosition = latlon_to_point2d(getPOIPosition(currIdx));
        
        //continue if screen does not contain POI or POI type is not correct
        if (visible.contains(poiPosition) != true || getPOIType(currIdx) != amenity) continue; 
        
        //iterates over rectangle array
        for (int n = 0; n < (gridParameter*gridParameter) - 1; n++) {
            //checks if current rectangle contains POI and push to POI grouping vector
            if (rectangleVec[n].contains(poiPosition)) {
                grid[n].push_back(poiPosition);
                totalPOIS += 1;
            }
        }
        
        //if the zoom level is below 2000 in width
        if (gridParameter == 1) {
            //draw POI image based on the type of amenity
            if(getPOIType(currIdx) == "cafe") {
                static ezgl::surface *s = g -> load_png("libstreetmap/resources/cafe_day.png");
                g -> draw_surface(s, poiPosition, 0.5 * 800 /visible.width());
            } 
            else if(getPOIType(currIdx) == "library") {
                static ezgl::surface *s = g->load_png("libstreetmap/resources/library.png");
                g->draw_surface(s, poiPosition, 0.5 * 800 /visible.width());
            } 
            else if(getPOIType(currIdx) == "library" || getPOIType(currIdx) == "school") {
                static ezgl::surface *s = g->load_png(lightMode?"libstreetmap/resources/school_day.png":"libstreetmap/resources/school_night.png");
                g->draw_surface(s, poiPosition, 0.5 * 800 /visible.width());
            }

            //if the width of the screen is less than 1600m
            if(visible.width() < 1600) {
                    poiPosition = ezgl::point2d(poiPosition.x, poiPosition.y - 30);
                    
                    //set colors
                    if(getPOIType(currIdx) == "cafe") g->set_color(255, 99, 71); 
                    else if(getPOIType(currIdx) == "library")  g->set_color(204,123,191); 
                    else if(getPOIType(currIdx) == "university") g->set_color(109, 79, 216); 
                    else g->set_color(ezgl::BLACK); 
            }

        }
    }
    //return if cluster not shown or no POIs exist in current view
    if (gridParameter == 1 || totalPOIS == 0) return;

    // loop over each grid row
    for (int row = 0; row < (gridParameter * gridParameter) - 1; row++){
        int poiCount = 0;
        double sum_x = 0.0;
        double sum_y = 0.0;

        for (auto it = grid[row].begin(); it != grid[row].end(); it++){
            poiCount += 1;
            sum_x += (*it).x;
            sum_y += (*it).y;
        }

        // calculate location to print
        double mean_x = sum_x / (double)poiCount;
        double mean_y = sum_y / (double)poiCount;
        ezgl::point2d meanPoint = ezgl::point2d(mean_x, mean_y);

        // draw to surface all of the coffee shops
        double scalingRatio = 0.21* (1 + (poiCount / totalPOIS)) * pow(gridParameter, 0.5);
        double divisor = initial_zoom / 240.0;
        
        // make POI using mean coordinates for rectangle
        ezgl::point2d numPOI = ezgl::point2d(mean_x, mean_y - (visible.width() / divisor));
        
        
        //add font formatting
        g->format_font("Noto Sans CJK", ezgl::font_slant::normal, ezgl::font_weight::bold);
        g->set_font_size(pow(visible.width(),0.1)*5);
        
        // check for each amenity case
        if(amenity == "cafe") {
            // get png for cafe icon
            static ezgl::surface *s = g->load_png("libstreetmap/resources/cafe_day.png");
            
            // set formatting
            g->draw_surface(s, meanPoint, scalingRatio);
            g->set_color(231, 111, 81);
            
            // draw POI count only if more than 1 POI
            if(poiCount > 1) g->draw_text(numPOI, std::to_string(poiCount));
            
        } 
        else if(amenity == "library") {
            // get png for library icon
            static ezgl::surface *s = g->load_png("libstreetmap/resources/library.png");
            
            // set formatting
            g->draw_surface(s, meanPoint, scalingRatio);
            g->set_color(217, 118, 194);
            
            // draw POI count only if more than 1 POI
            if(poiCount > 1) g->draw_text(numPOI, std::to_string(poiCount));
        } 
        else {
            static ezgl::surface *s = g->load_png(lightMode?"libstreetmap/resources/school_day.png":"libstreetmap/resources/school_night.png");
            
            //set formatting
            g->draw_surface(s, meanPoint, 0.3);
            g->set_color(lightMode?ezgl::color(109, 79, 216):ezgl::color(142, 168, 244));
            
            // draw POI count only if more than 1 POI
            if (poiCount > 1) g->draw_text(numPOI, std::to_string(poiCount));
        }
        //revert bold font
        g->format_font("Noto Sans CJK", ezgl::font_slant::normal, ezgl::font_weight::normal);
    }
}

// function: show food                       
// shows all the food in that was searched
void show_food(ezgl::renderer *g) {
    // get visible world
    ezgl::rectangle visible = g -> get_visible_world();

    // loop through all the fast food restaurants that are available on the map 
    // and highlight them as a dot
    for (int i = 0; i < allFoods.size(); i ++) {
           g->set_color(255, 99, 71);     
           g->fill_arc(allFoods[i], visible.width() / 200, 0, 360);

    }
}

// label intersections with a dot
void label_intersections(ezgl::renderer *g) {
    // set colour and get visible world
    ezgl::rectangle visible = g->get_visible_world();
    g->set_color(200, 200, 200);
    double scalingRatio = .5 -visible.width()/BIG_MAP;
    scalingRatio = scalingRatio > 0 ? scalingRatio : .1;

    // // prints every single intersection in the map
    // for (int i = 0; i < intersectionPoint2d.size(); i++){
    //     if (visible.contains(intersectionPoint2d[i]) != true || (visible.width() > 5000))
    //         continue;
    //     g->fill_arc(intersectionPoint2d[i], visible.width() / 300, 0, 360);
    // }

    // checks that first intersection exists and two intersection button is clicked
    if (twoIntersections.first != -1 && twoIntersectClick){
        // loag png for start icon and display it at start position
        static ezgl::surface *starticon = g->load_png("libstreetmap/resources/start_cent.png");
        g->draw_surface(starticon, latlon_to_point2d(getIntersectionPosition(twoIntersections.first)), scalingRatio);
    }

}

// function: highlight_intersections
// highlight intersections upon mouse press
void highlight_intersections(ezgl::application *application, double x, double y) {
    // checks that the two intersection button hasn't been clicked
    if (!twoIntersectClick) intersectToggle = !intersectToggle;
    currIntersect = ezgl::point2d(x, y);

    // g -> set_visible_world(ezgl::rectangle(ezgl::point2d(x-1000,y-1000),ezgl::point2d(x+1000,y+1000)));
    int closeIntersect = findClosestIntersection(point2d_to_latlon(currIntersect));
    
    // if intersection is already selected or we are selecting two intersections
    if (intersectToggle || twoIntersectClick){
        // initialize variables to check for intersections clicked, if unknown tell user road does not exist
        std::string closestIntersectName = getIntersectionName(closeIntersect);
        std::string unknownRoad = "unknown";

        // look for unknown road
        std::size_t found = closestIntersectName.find(unknownRoad);
        application ->refresh_drawing();

        //if road is not unknown
        if (found == std::string::npos){
            currIntersect = intersectionPoint2d[closeIntersect];

            // update bottom screen prompt
            application->update_message(closestIntersectName.c_str());
            if(twoIntersectClick){
                // resets intersection pair
                if (twoIntersections.second != -1) twoIntersections = std::make_pair(-1, -1);
                // sets the first intersection
                if(twoIntersections.first == -1) twoIntersections.first = closeIntersect; 
                else{
                    //sets the second intersection
                    twoIntersections.second = closeIntersect; 
                    
                    // draw the path to destination and show it
                    draw_intpath(application->get_renderer());
                    showPathNames(application->get_renderer());
                    application -> flush_drawing();

                    // display directions
                    display_directions();

                    //////////////////// M2 code /////////////////////
                    // GtkWidget *twoInt_area; // the content area of the dialog
                    // GtkWidget *twoInt_label; // the label we will create to display a message in the content area
                    // GtkWidget *twoInt_dialog; // the dialog box we will create

                    // twoInt_dialog = gtk_dialog_new();
                    
                    // //Create a label and attach it to the content area of the dialog
                    // twoInt_area = gtk_dialog_get_content_area(GTK_DIALOG(twoInt_dialog));
                    // std::string message_label = "Directions from \n" + getIntersectionName(twoIntersections.first) + "\n to \n" + getIntersectionName(twoIntersections.second); 
                    
                    // twoInt_label = gtk_label_new(message_label.c_str());
                    // gtk_container_add(GTK_CONTAINER(twoInt_area), twoInt_label);

                    // gtk_widget_show_all(twoInt_dialog);
                   // twoIntersections = std::make_pair (-1, -1);
                }
            }
        }
        else {
            // error checking for invalid roads
            application->update_message("Road Does Not Exist - Click Eleswhere");
            currIntersect = ezgl::point2d(0, 0);
            if (!twoIntersectClick) intersectToggle = !intersectToggle;
        }
    }
    application->refresh_drawing();
}

//function: click on poi
void poi_click(ezgl::application *application, double x, double y){
    
    // set up rendering variables
    ezgl::renderer *g = application->get_renderer();
    ezgl::rectangle visible = g->get_visible_world();
    
    LatLon currPos = point2d_to_latlon(ezgl::point2d(x,y));
    
    // zoom is close display text of POI in dialog box
    if (visible.width() < 1600) {
        std::vector<int> POIcandidates = {findClosestPOI(currPos, "cafe"), findClosestPOI(currPos, "library"),
            findClosestPOI(currPos, "school"), findClosestPOI(currPos, "university")};

        int minDistance = findDistanceBetweenTwoPoints(std::make_pair(currPos, getPOIPosition(POIcandidates[0]))); 
        int currPOI = POIcandidates[0]; 
        
        // find the closest POI
        for(int i=0; i < 3; i++){
            if(findDistanceBetweenTwoPoints(std::make_pair(currPos, getPOIPosition(POIcandidates[i+1]))) < minDistance){
                currPOI = POIcandidates[i+1]; 
                minDistance = findDistanceBetweenTwoPoints(std::make_pair(currPos, getPOIPosition(POIcandidates[i+1]))); 
            }
        }
        
        if(minDistance < 500){
            // get widget dialog box, content area of the dialog boxes
            // label for dialog box and the dialog box we will create
            GtkWidget *poi_content_area;
            GtkWidget *poi_label;
            GtkWidget *poi_dialog; 

            // create a new dialog box
            poi_dialog = gtk_dialog_new();
            
            //Create a label and attach it to the content area of the dialog
            poi_content_area = gtk_dialog_get_content_area(GTK_DIALOG(poi_dialog));
            std::string message_label = "Selected " + getPOIName(currPOI); 
            
            // add message to dialog box
            poi_label = gtk_label_new(message_label.c_str());
            gtk_container_add(GTK_CONTAINER(poi_content_area), poi_label);

            // display the dialgo box
            gtk_widget_show_all(poi_dialog);
        }
    }


}

// function: intersect_toggle
// toggles intersections
void intersect_toggle(ezgl::renderer *g, ezgl::point2d drawPosition) {
    // if position is invalid return
    if (drawPosition == ezgl::point2d(0, 0)) return;
    ezgl::rectangle visible = g->get_visible_world();
    
    //formatting for intersection highlighting
    lightMode? g->set_color(116,0,184) : g->set_color(254,69,189);
    g->fill_arc(drawPosition, visible.width() / 200, 0, 360);
}

// function: initial_setup
// call before activation of application to create buttons and connect widgets to their
// callback functions
void initial_setup(ezgl::application *application, bool /*new_window*/) {
    // get renderer and set the font
    ezgl::renderer *g = application->get_renderer();
    g->format_font("Noto Sans CJK", ezgl::font_slant::normal, ezgl::font_weight::normal);

    // create a pointer to the object findIntersection button and connect search update function to the signal
    GObject *findIntersectionButton = application->get_object("FindIntersection");
    g_signal_connect(findIntersectionButton, "clicked", G_CALLBACK(go_button), application);

    // create pointers to the two search drop box text in glade
    GObject *searchFirstStreet = application->get_object("FirstStreet");
    GObject *searchSecondStreet = application->get_object("SecondStreet");
    GObject *searchThirdStreet = application->get_object("ThirdStreet");
    GObject *searchFourthStreet = application->get_object("FourthStreet");

    // connect the signals for the two search drop box text
    g_signal_connect(searchFirstStreet, "changed", G_CALLBACK(searchForFirst), application);
    g_signal_connect(searchSecondStreet, "changed", G_CALLBACK(searchForSecond), application);
    g_signal_connect(searchThirdStreet, "changed", G_CALLBACK(searchForThird), application);
    g_signal_connect(searchFourthStreet, "changed", G_CALLBACK(searchForFourth), application);

    // get object combo box tezt
    GtkComboBoxText *comboBox = (GtkComboBoxText *) application -> get_object("newMap");

    std::vector<std::string> names;
    names.reserve(places.size());

    // gets keys of cities
    for (auto kv: places) {
        names.push_back(kv.first);
    }
    
    //display cities in combo box
    for (int i = 0; i < names.size(); i ++) {
        const gchar* name = names[i].c_str();
        gtk_combo_box_text_append(comboBox, NULL, name);
    }

    g_signal_connect(comboBox, "changed", G_CALLBACK(changeMap), application);

    // Create buttons and link it with ""_button callback fn
    GObject *toggleDarkMode = application -> get_object("darkMode");
    GObject *displaySubways = application -> get_object("subways");
    GObject *displayCoffeeShops = application -> get_object("coffeeShops");
    GObject *displayNewMap = application -> get_object("showNewMap");
    GObject *displaySchoolLife = application ->get_object("schoolLife");
    GObject *displayFoodNearMe = application -> get_object("foodNearMe");

    //for m3, click 2 intersections and help manual
    GObject *displayTwoIntersects = application -> get_object("twoIntersects");
    GObject *displayUserManual = application -> get_object ("Help");
    GObject *findPOI = application -> get_object("findPOI");

    // linking signals for buttons and callback functions for ALL buttons
    g_signal_connect(toggleDarkMode, "clicked", G_CALLBACK(dark_mode_button), application);
    g_signal_connect(displaySubways, "clicked", G_CALLBACK(subways_button), application);
    g_signal_connect(displayCoffeeShops, "clicked", G_CALLBACK(coffee_shops_button), application);
    g_signal_connect(displayNewMap, "clicked", G_CALLBACK(new_map_button), application);
    g_signal_connect(displaySchoolLife, "clicked", G_CALLBACK(school_life_button), application);
    g_signal_connect(displayFoodNearMe, "clicked", G_CALLBACK(food_near_me_button), application);
    g_signal_connect(displayTwoIntersects, "clicked", G_CALLBACK(twoIntersect_button), application);
    g_signal_connect(displayUserManual, "clicked", G_CALLBACK(help_button), application);

    // connect the signals for the drop box text
    g_signal_connect(findPOI, "changed", G_CALLBACK(searchforFood), application);
}

// calback function: changeMap
// allows the user to choose a place and map to change
gboolean changeMap(GtkWidget * /*widget*/, gpointer data){
    auto application = static_cast<ezgl::application *>(data);

    // get object for new map possibilities
    GtkComboBoxText *comboBox = (GtkComboBoxText *) application -> get_object("newMap");

    // get content from combo box which gives the map path to load new map
    const gchar* entry = gtk_combo_box_text_get_active_text(comboBox);
    
    // typecast as string and assign to new map
    std::string chosen = entry;
    newMap = entry;

    return true;
}

// callback function: searchForFirst
// finds name of street from user input and autocompletes
gboolean searchForFirst(GtkWidget * /*widget*/, gpointer data) {
    if (dropDown){
        auto application = static_cast<ezgl::application *>(data);
        dropDown = false;

        // variable to check for unknown roads
        std::string unknownRoad = "<unknown>";

        // initialize pointers to combo box and entry objects in application
        GtkComboBoxText *firstComboBoxText = (GtkComboBoxText *)application -> get_object("FirstStreet");
        GtkEntry *searchFirstEntry = (GtkEntry *)application -> get_object("searchFirstStreet");

        // clear all existing entries in combo box text
        gtk_combo_box_text_remove_all(firstComboBoxText);

        // get user input and typecast into a string
        const char *entry = gtk_entry_get_text(searchFirstEntry);
        std::string search = entry;

        // initialize a vector to store all the partial name ids that match user input using
        // the function from m1.cpp takes a string as argument
        std::vector<StreetIdx> partialNameIDs;
        partialNameIDs = findStreetIdsFromPartialStreetName(search);
        std::string str;

        // iterates through the partial names vector and appends them to the combo box text as a drop down menu
        for (int i = 0; i < partialNameIDs.size(); i++){
            // if street before is the same or street is unknown , do not append
            if (str != getStreetName(partialNameIDs[i]) && getStreetName(partialNameIDs[i]) != unknownRoad){
                // get street name and typcast into a cstring
                str = getStreetName(partialNameIDs[i]);
                const gchar* streetName = str.c_str();
        
                // append to drop down menu
                gtk_combo_box_text_append_text(firstComboBoxText, streetName);
            }
        }

        // get the text that the user has chosen
        const char *chosen = gtk_combo_box_text_get_active_text(firstComboBoxText);
        std::string firstSt = chosen;
        
        // find the streetID of chosen street and put into a global pair variable for intersection searching
        for (int i = 0; i < partialNameIDs.size(); i++) {
            // get current street name
            str = getStreetName(partialNameIDs[i]);

            // if first street is equal to the current street assign to first intersection of intersection 1
            if (firstSt == str) { 
                street1OfIntersection1 = partialNameIDs[i];
                break;
            }
        }
        dropDown = true;
    }
    return true;
}

// callback function:searchForSecond
// finds name of second street from user input and autocompletes
gboolean searchForSecond(GtkWidget * /*widget*/, gpointer data){
    if (dropDown) {
        auto application = static_cast<ezgl::application *>(data);
        dropDown = false;

        // variables to check for unknown roads
        std::string unknownRoad = "<unknown>";
        const char *unknown = unknownRoad.c_str();

        // typecast comboboxtext and entry from an object
        GtkComboBoxText *secondComboBoxText = (GtkComboBoxText *)application->get_object("SecondStreet");
        GtkEntry *searchSecondEntry = (GtkEntry *)application->get_object("searchSecondStreet");

        // clear all existing entries in combo box text
        gtk_combo_box_text_remove_all(secondComboBoxText);

        // get user input and typecast into a string
        const char *entry = gtk_entry_get_text(searchSecondEntry);
        std::string search = entry;

        // initialize a vector to store all the partial name ids that match user input using
        // the function from m1.cpp takes a string as argument
        std::vector<StreetIdx> partialNameIDs;
        partialNameIDs = findStreetIdsFromPartialStreetName(search);

        // iterates through the partial names vector and appends them to the combo box text as a drop down menu
        for (int i = 0; i < partialNameIDs.size(); i++) {   
            // make a pair with the street from previous search box and current partial name
            std::pair<StreetIdx, StreetIdx> twoStreets;
            twoStreets = std::make_pair(street1OfIntersection1, partialNameIDs[i]); 
            // get a vector of all the intersection ids
            std::vector<IntersectionIdx> intersections1 = findIntersectionsOfTwoStreets(twoStreets);
            std::string str;
            
            // loop through all the intersection ids and append to the combo box names of intersections
            for (int j = 0; j < intersections1.size(); j ++) {
                if (str != getIntersectionName(intersections1[j])) {
                    str = getIntersectionName(intersections1[j]);
                    const gchar* intersectionName = str.c_str();
                    
                    // only append if no unknown road
                    if (strncmp (unknown, intersectionName, unknownRoad.size()) != 0) {
                        // add each displayed intersection list
                        // append to drop down menu
                        gtk_combo_box_text_append_text(secondComboBoxText, intersectionName);
                        intersectionOfStreets1.push_back(intersections1[j]);
                    }
                }
            }
        }

        // get the text that the user has chosen
        const char *chosen = gtk_combo_box_text_get_active_text(secondComboBoxText);
        std::string secSt = chosen;

        // loop through and check which intersection is the chosen one, return interesectionID
        for (int i = 0; i < intersectionOfStreets1.size(); i ++) {
                std::string str = getIntersectionName(intersectionOfStreets1[i]);
                // if name of intersection == name chosen, assign to start intersection
                if (secSt == str) {
                    startIntersection = intersectionOfStreets1[i];
                }   
        }
        dropDown = true;
    }
    return true;
}

// callback function: searchForThird
// finds name of street from user input and autocompletes
gboolean searchForThird(GtkWidget * /*widget*/, gpointer data) {
    if (dropDown) {
        auto application = static_cast<ezgl::application *>(data);
        dropDown = false;

        // variable to check for unknown roads
        std::string unknownRoad = "<unknown>";

        // initialize pointers to combo box and entry objects in application
        GtkComboBoxText *thirdComboBoxText = (GtkComboBoxText *)application -> get_object("ThirdStreet");
        GtkEntry *searchThirdEntry = (GtkEntry *)application -> get_object("searchThirdStreet");

        // clear all existing entries in combo box text
        gtk_combo_box_text_remove_all(thirdComboBoxText);

        // get user input and typecast into a string
        const char *entry = gtk_entry_get_text(searchThirdEntry);
        std::string search = entry;

        // initialize a vector to store all the partial name ids that match user input using
        // the function from m1.cpp takes a string as argument
        std::vector<StreetIdx> partialNameIDs;
        partialNameIDs = findStreetIdsFromPartialStreetName(search);
        std::string str;
        
        // iterates through the partial names vector and appends them to the combo box text as a drop down menu
        for (int i = 0; i < partialNameIDs.size(); i++) {
            // if street before is the same or street is unknown , do not append
            if (str != getStreetName(partialNameIDs[i]) && getStreetName(partialNameIDs[i]) != unknownRoad){
                // get street name and typcast into a cstring
                str = getStreetName(partialNameIDs[i]);
                const gchar* streetName = str.c_str();
        
                // append to drop down menu
                gtk_combo_box_text_append_text(thirdComboBoxText, streetName);
            }
        }

        // get the text that the user has chosen
        const char *chosen = gtk_combo_box_text_get_active_text(thirdComboBoxText);
        std::string firstSt = chosen;
        
        // find the streetID of chosen street and put into a global pair variable for intersection searching
        for (int i = 0; i < partialNameIDs.size(); i++) {
            // get current street name
            str = getStreetName(partialNameIDs[i]);

            // if first street is equal to the current street assign to the first street of intersection 2
            if (firstSt == str) { 
                street1OfIntersection2 = partialNameIDs[i];
                break;
            }
        }
        dropDown = true;
    }
    return true;
}

// callback function:searchForFourth
// finds name of second street from user input and autocompletes
gboolean searchForFourth(GtkWidget * /*widget*/, gpointer data){
    if (dropDown) {
        auto application = static_cast<ezgl::application *>(data);
        dropDown = false;

        // variables to check for unknown roads
        std::string unknownRoad = "<unknown>";
        const char *unknown = unknownRoad.c_str();

        // typecast comboboxtext and entry from an object
        GtkComboBoxText *fourthComboBoxText = (GtkComboBoxText *)application->get_object("FourthStreet");
        GtkEntry *searchFourthEntry = (GtkEntry *)application->get_object("searchFourthStreet");

        // clear all existing entries in combo box text
        gtk_combo_box_text_remove_all(fourthComboBoxText);

        // get user input and typecast into a string
        const char *entry = gtk_entry_get_text(searchFourthEntry);
        std::string search = entry;

        // initialize a vector to store all the partial name ids that match user input using
        // the function from m1.cpp takes a string as argument
        std::vector<StreetIdx> partialNameIDs;
        partialNameIDs = findStreetIdsFromPartialStreetName(search);

        // iterates through the partial names vector and appends them to the combo box text as a drop down menu
        for (int i = 0; i < partialNameIDs.size(); i++) {
            // make a pair with the street from previous search box and current partial name
            std::pair<StreetIdx, StreetIdx> twoStreets;
            twoStreets = std::make_pair(street1OfIntersection2, partialNameIDs[i]); 
            std::vector<IntersectionIdx> intersections2 = findIntersectionsOfTwoStreets(twoStreets);
            std::string str;

            // loop through all the intersection ids and append to the combo box names of intersections
            for (int j = 0; j < intersections2.size(); j ++) {
                if (str != getIntersectionName(intersections2[j])) {
                    str = getIntersectionName(intersections2[j]);
                    const gchar* intersectionName = str.c_str();
                    
                    // only append if no unknown road
                    if (strncmp (unknown, intersectionName, unknownRoad.size()) != 0) {
                        // append to drop down menu
                        gtk_combo_box_text_append_text(fourthComboBoxText, intersectionName);
                        // add each displayed intersection list
                        intersectionOfStreets2.push_back(intersections2[j]);
                    }
                }
            }
        }

        // get the text that the user has chosen
        const char *chosen = gtk_combo_box_text_get_active_text(fourthComboBoxText);
        std::string secSt = chosen;

        // loop through and check which intersection is the chosen one, return interesectionID
        for (int i = 0; i < intersectionOfStreets2.size(); i ++) {
                std::string str = getIntersectionName(intersectionOfStreets2[i]);

                // if name of intersection == name chosen, assign to start intersection
                if (secSt == str)
                    destinationIntersection = intersectionOfStreets2[i];
        }
        dropDown = true;
    }
    return true;
}

// callback function: searchforPOI
// searches for food and coffee shops
gboolean searchforFood(GtkWidget * /*widget*/, gpointer data) {
    auto application = static_cast<ezgl::application *>(data);

    // initialize pointers to combo box and entry objects in application
    GtkEntry *searchPOI = (GtkEntry *)application -> get_object("findPOI");

    // get user input and typecast into a string
    const char *entry = gtk_entry_get_text(searchPOI);
    std::string search = entry;

    // initialize a vector to store all the POI ids that match user input using
    // the function from m1.cpp takes a string as argument
    std::vector<POIIdx> POIids;

    // loop through all points of interests and get cafe and food
    for(int currIdx = 0; currIdx < getNumPointsOfInterest(); currIdx++){
        if(getPOIType(currIdx) == "fast_food")
            POIids.push_back(currIdx);
    }
    
    // clear the vector for new information
    allFoods.clear();

    //iterate through the POIs
    for (int currPOI = 0; currPOI < POIids.size(); currPOI ++){
        //if match is found
        if (parseName(getPOIName(currPOI)) == parseName(search)){
            // get the position of the poi and add the position to the vector to display
            LatLon position = getPOIPosition(currPOI);
            allFoods.push_back(latlon_to_point2d(position));
        }
    }
    // clear the vector for new information
    POIids.clear();
    return true;
}

// callback function: find_intersection_button
// finds intersection using input given by the user
void go_button(GtkWidget */*widget*/, ezgl::application *application) {
    //application -> update_message("find intersection button pressed");
    //make sure only one path is shown
    
    if(twoIntersectClick){
         application->update_message("Please Turn Click Mode Off \U0001F926");
         return;
    }
    if(destinationIntersection == -1 || startIntersection == -1){
         application->update_message("Please Fill All Boxes! \U0001F926");
         return;
    }

    // toggle go
    GO = !GO;

    // clear global variables for next search to occur
    intersectionOfStreets1.clear();
    intersectionOfStreets2.clear();
    
    // create pair for two intersections
    twoIntersections = std::make_pair (startIntersection, destinationIntersection);
   
    //show the orange flag
    ezgl::renderer *g = application->get_renderer();

    //draw all paths
    draw_intpath(g);
    showPathNames(g);
    draw_orang_flag(g);

    application -> flush_drawing();

    // if no legal path exists to destination display a message
    if (segPath.size() == 0) application -> update_message("No legal path exists to destination.");
    
    std::cout << "here" << std::endl;
    // here call the function to draw map and display
    display_directions();
    twoIntersections = std::make_pair (-1, -1);
   
    /////////// M2 implementation /////////////

    // // loop through intersections of street 1 and 2 and find matching intersections
    // for (int i = 0; i < intersectionOfStreets1.size(); i ++) {
    //     for (int j = 0; j < intersectionOfStreets2.size(); j ++){
    //         // find the intersection between the two streets
    //         displayIntersection = findIntersectionsOfTwoStreets(std::make_pair(intersectionOfStreets1[i], intersectionOfStreets2[j]));
    //         if (displayIntersection.size() > 0){
    //             found = true;
    //             break;
    //         }
    //     }
    //     if (found) break;
    // }
    
    // // if there is a match, highlight it
    // if (displayIntersection >= 0){
    //     // center the display
    //     ezgl::point2d position = latlon_to_point2d(getIntersectionPosition(startIntersection));
    //     g -> set_visible_world(ezgl::rectangle(ezgl::point2d(position.x-500,position.y-500),ezgl::point2d(position.x+500,position.y+500)));
        
    //     //refresh 
    //     application -> refresh_drawing();
    //     visible = g-> get_visible_world();
        
    //     //formatting - show bubble
    //     g -> set_color(207, 186,240);
    //     g -> fill_arc(position, visible.width()/150, 0, 360);
    //     application -> flush_drawing();
    // }
    // else
    // {
    //     // no intersection
    //     application->refresh_drawing();
    //     application->update_message("no such intersection");
    // }
    // do something with the intersection Idx
}

void draw_orang_flag(ezgl::renderer *g){
    ezgl::rectangle visible = g->get_visible_world(); 
    double scalingRatio = .5 -visible.width()/BIG_MAP;
    scalingRatio = scalingRatio > 0 ? scalingRatio : .1;
    static ezgl::surface *starticon = g->load_png("libstreetmap/resources/start_cent.png");
    g->draw_surface(starticon, latlon_to_point2d(getIntersectionPosition(twoIntersections.first)), 
            scalingRatio);

}
// callback function: dark_mode_button
// toggles the dark mode button
void dark_mode_button(GtkWidget * /*widget*/, ezgl::application *application) {
    // toggle the button and change the text on the button to reflect the change
    lightMode? application-> change_button_text("", "Light Mode"): application -> change_button_text("", "Dark Mode");
    lightMode = !lightMode;
    application -> refresh_drawing();
}

// callback function: subways_button
// toggles the subways button
void subways_button(GtkWidget * /*widget*/, ezgl::application *application) {
    // get renderer to draw to the canvas
    ezgl::renderer *g = application->get_renderer();

    // togggles subways button
    showSubways = !showSubways;

    // draw subways to canvas if toggle = true else refresh page
    if (showSubways){
        show_subways(g);
        application->flush_drawing();
    }
    else
        application->refresh_drawing();
}

// callback function: twoIntersect_button
// toggles the twoIntersect button
void twoIntersect_button(GtkWidget * /*widget*/, ezgl::application *application) {
    // get renderer to draw to the canvas
    if(GO) GO = false; 
    // togggle button
    twoIntersectClick = !twoIntersectClick;

    // Remember to draw intersections to canvas if toggle = true else refresh page
    if (twoIntersectClick)
        application->flush_drawing();
    else
        application->refresh_drawing();
}

// callback function: coffee_shops_button
// toggles the coffee shops button
void coffee_shops_button(GtkWidget * /*widget*/, ezgl::application *application) {
    // get renderer to draw to the canvas
    ezgl::renderer *g = application->get_renderer();

    // toggles subways button
    showCafes = !showCafes;

    // draws cafes to canvas if toggle = true
    if (showCafes){
        show_cafes(g, "cafe");
        application->flush_drawing();
    }
    else
        application->refresh_drawing();
}

// callback function: school_life_button
// toggles the school life button
void school_life_button(GtkWidget * /*widget*/, ezgl::application *application) {
    // get renderer to draw to the canvas
    ezgl::renderer *g = application->get_renderer();

    // toggles subways button
    showSchoolLife = !showSchoolLife;

    // draws cafes to canvas if toggle = true
    if (showSchoolLife){
        show_cafes(g, "library");
        show_cafes(g, "school");
        show_cafes(g, "university");
        application->flush_drawing();
    }
    else{
        application->refresh_drawing();
    }
}

// find nearby fast food chains
void food_near_me_button(GtkWidget * /*widget*/, ezgl::application *application) {
    ezgl::renderer *g = application->get_renderer();

    // toggles show food
    showFood = !showFood;

    // if show foods is true, show foods
    if (showFood){
        show_food(g);
        application -> flush_drawing();
        application -> update_message("Showing food near you");
    }
    else{ // refresh drawings
        application -> refresh_drawing();
        application -> update_message("Not available near you");
    }

}

// button to load a different map without compilation
void new_map_button(GtkWidget * /*widget*/, ezgl::application *application) {
    //don't respond if no input;
    if (newMap == "") return; 

    //close the current map, empty variables 
    // commented out code can produce a dialog box = optional ?
    // get widget and pointers for dialog box content area, label and dialog
    GtkWidget *poi_content_area;
    GtkWidget *poi_label; 
    GtkWidget *poi_dialog;

    // create new dialog box
    poi_dialog = gtk_dialog_new();

    //Create a label and attach it to the content area of the dialog
    poi_content_area = gtk_dialog_get_content_area(GTK_DIALOG(poi_dialog));
    std::string message_label = "Loaded " + newMap; 
    poi_label = gtk_label_new(message_label.c_str());
    gtk_container_add(GTK_CONTAINER(poi_content_area), poi_label);

    // display dialog box
    gtk_widget_show_all(poi_dialog);

    // close the map and delete all memory
    closeMap();

    // load new map
    loadMap(places[newMap]);
    application -> change_canvas_world_coordinates("MainCanvas", ezgl::rectangle({x_from_lon(min_lon), y_from_lat(min_lat)}, {x_from_lon(max_lon), y_from_lat(max_lat)}));
    
    //display this change 
    application -> refresh_drawing(); 
}

// help button that pops up a dialog box with a user manual
void help_button(GtkWidget * /*widget*/) {
    // get widget and pointers for dialog box content area, label and dialog
    GtkWidget *poi_content_area; 
    GtkWidget *poi_label; 
    GtkWidget *poi_dialog; 

    // create new dialog box
    poi_dialog = gtk_dialog_new();
    
    //Create a label and attach it to the content area of the dialog
    poi_content_area = gtk_dialog_get_content_area(GTK_DIALOG(poi_dialog));
    std::string message_label = "USER MANUAL \U0001F913 \n\n";
    message_label += "DIRECTIONS \U0001F4CC \nTo change modes from user input to user mouse click -- toggle 'Click Mode' button \n\nTo find directions from two intersections through input: \U0001F698\n";
    message_label += "1. Enter the first street in the first search entry and select a street. \n2. Enter the second street of intersection into the second search entry and select the intersection. \n";
    message_label += "3. Repeat steps 1 and 2 with the second intersection. \n4. Press GO to display directions. \n\nTo find directions by clicking: \U0001F697 \n1. Toggle the 'Click Mode' button. \n";
    message_label += "2. Using your mouse, click on the position you would like to start navigation from. \n3. Using your mouse, click on the destination and directions will automatically pop up in a display box. \n\n"; 
    
    message_label += "LOAD NEW MAP \U0001F389 \n";
    message_label += "1. To load a new map, click on the dropdown menu on the top right corner and select the destination map to load. \n2. Click the 'New Map' button and wait for the new map to load.\n";
    message_label += "3. A message will pop up to inform you when the map is done loading\n\n";
    message_label += "FIND FOOD \U0001F354 \n";
    message_label += "1. Type in any fast food restaurant into the search bar labeled 'Food near me'\n2. Click the 'Find Food' \nbutton to display all the fast food restaurants that match the search in the area \n\n";

    message_label += "COFFEE SHOPS \U00002615/ SCHOOL LIFE \U0001F3EB/ SUBWAYS \U0001F687/ DARK MODE \U0001F319 \n";
    message_label += "1. Toggle the buttons with the coffee shop icon to display coffee shops, purple graduation hat icon to display schools and libraries \n";
    message_label += "subway icon to display subway stations and moon icon to toggle between light and dark mode";

    // add label to dialog box
    poi_label = gtk_label_new(message_label.c_str());
    gtk_container_add(GTK_CONTAINER(poi_content_area), poi_label);

    // show dialog box
    gtk_widget_show_all(poi_dialog);
}

// act on intersection highlight
void act_on_mouse_press(ezgl::application *application, GdkEventButton *event, double x, double y) {
    event->button = 0; 
    // show names of pois if mouse click and button clicked
    if (showCafes || showSchoolLife) poi_click(application, x, y);

    // highlight intersection on mouse click otherwise
    else highlight_intersections(application, x, y);
}

//draw the path on the screen
void draw_intpath(ezgl::renderer *g){
    
    // check that intersections have been selected
    if(twoIntersections.first == -1 || twoIntersections.second == -1) return; 

    // resets the path vector iff it exists
    if(segPath.size() > 0)
        segPath.clear();

    // get the map area
    ezgl::rectangle visible = g->get_visible_world(); 

    // get the path
    segPath = findPathBetweenIntersections(15, twoIntersections);

    // scale the POIs based on the map size
    double scalingRatio = .5 -visible.width()/BIG_MAP;
    scalingRatio = scalingRatio > 0 ? scalingRatio : .1;

    //checks for valid path
    if(segPath.size() == 0)
        return;

    // sets line formatting
    g->set_color(226, 81, 65);
    g->set_line_width(mainRoadWidth);

    // loops through segments in path and draws them
    for(auto seg : segPath){

        //gets the current seg info struct
        StreetSegmentInfo currSegInfo = getStreetSegmentInfo(seg);

        //gets the number of curve points for the given street segment
        int currSegCurves = currSegInfo.numCurvePoints;
        
        // draws curved segments if any exist
        if (currSegCurves > 0){

            // draws the first partial segment
            g->draw_line(latlon_to_point2d(getIntersectionPosition(currSegInfo.from)), 
                latlon_to_point2d(getStreetSegmentCurvePoint(0, seg)));
            
            // draws the middle partial segments
            for (int currCurve = 0; currCurve < currSegCurves-1; currCurve++){
                LatLon firstPoint = getStreetSegmentCurvePoint(currCurve, seg);
                LatLon secondPoint = getStreetSegmentCurvePoint(currCurve+1, seg);
                g->draw_line(latlon_to_point2d(firstPoint), latlon_to_point2d(secondPoint));
            }

            //draws the last segment
            g->draw_line(latlon_to_point2d(getStreetSegmentCurvePoint(currSegCurves-1, seg)), 
                latlon_to_point2d(getIntersectionPosition(currSegInfo.to)));
        }
        else{
            //draws the straight line if no curve points exist
             g->draw_line(latlon_to_point2d(getIntersectionPosition(currSegInfo.to)), 
                latlon_to_point2d(getIntersectionPosition(currSegInfo.from)));
        }
        
       
    }
    static ezgl::surface *endicon = g->load_png("libstreetmap/resources/end_cent.png");
    g->draw_surface(endicon,latlon_to_point2d(getIntersectionPosition(twoIntersections.second)), 
        scalingRatio);
}

// function: display_directions()
// this function displays the directions needed to get from start to destination
void display_directions(){

    // check if no legal path (empty vector), if no legal path simply return
    if(segPath.size() == 0){
        return;
    }

    // get the streetsegments to iterate through in order to display directions
    std::vector<StreetSegmentIdx> streetSegs = segPath;

    // setting up pointers to for each dialog box
    // content area of dialog, display message in area and the dialog box
    GtkWidget *main_content_area;
    GtkWidget *main_label; 
    GtkWidget *main_dialog; 

    // pointers for the sub dialog box
    // content area of dialog, display message in area and the dialog box
    GtkWidget *sub_content_area; 
    GtkWidget *sub_label; 
    GtkWidget *sub_dialog; 

    // create the two dialog boxes
    main_dialog = gtk_dialog_new();
    sub_dialog = gtk_dialog_new();

    // define local variables to be used when checking and displaying directions
    // distance for distances between road and total diatances, unknownRoad to check for unknown roads
    int distance = 0;
    std::string unknownRoad = "<unknown>";

    //Create a label and attach it to the content area of the dialog and sub dialog bog
    main_content_area = gtk_dialog_get_content_area(GTK_DIALOG(main_dialog));
    sub_content_area = gtk_dialog_get_content_area(GTK_DIALOG(sub_dialog));

    // calculate total distance travelled from start to destination by adding distance of each segment to distance
    for (int i = 0; i < streetSegs.size(); i ++){
        // get distance of current street segment and add to total distance
        distance += findStreetSegmentLength(streetSegs[i]);
    }

    // display title message 
    std::string message_label = "FROM: " + getIntersectionName(twoIntersections.first) +  " \nTO: " + getIntersectionName(twoIntersections.second) + "\n\n";
    
    // get total travel time
    int travelTime = computePathTravelTime(15, streetSegs) / 60;

    // display estimated travel time 
    if (travelTime == 1) // if == 1, minute
        message_label += "ETA: " + std::to_string(travelTime) + " minute\n";
    else   // minutes
        message_label += "ETA: " + std::to_string(travelTime) + " minutes\n";
    
    // displays km instead of m if > 1000m
    if (distance < 1000)
        message_label += "Estimated distance: " + std::to_string(distance) + " m\n\n";
    if (distance >= 1000){
        // convert number to one decimal place
        float display = (distance/1000.0);
        float distance2 = floorf(display * 10)/ 10;

        // set precision choses how many digits to display and typecast to string
        std::stringstream stream;
        if (distance < 10) // display 2 digits
            stream.precision(2);
        if (distance > 9 && distance < 100) // display 3 digits
            stream.precision(3);
        if (distance > 99) // display 4 digits
            stream.precision(4);
        stream << distance2;
        std::string str = stream.str();

        // add on to the message label the estimated distance to destination
        message_label += "Estimated distance: " + str + " km\n\n";
    }

    // initialize a counter to display number of direction steps
    int numberOfDirections = 1;
    bool mainDisplayPrinted = false; 

    // reset distance variable straight path
    distance = 0;

    for (int i = 0; i < (streetSegs.size() - 1 ); i ++) {
      //  std::cout << numberOfDirections<<"\n"; 

        if(numberOfDirections > 30 && mainDisplayPrinted ==false){
            //createa a new dialog box if there are too many directions
            main_label = gtk_label_new(message_label.c_str());
            gtk_container_add(GTK_CONTAINER(main_content_area), main_label);    
            gtk_widget_show_all(main_dialog);
        
            message_label = "";
            mainDisplayPrinted = true;
        }
        // get street segment info for the curr and next segment
        StreetSegmentInfo currSeg = getStreetSegmentInfo(streetSegs[i]);
        StreetSegmentInfo nextSeg = getStreetSegmentInfo(streetSegs[i + 1]);

        // if curr seg and next seg are of same street, add to distance travelled on this street
        if (currSeg.streetID == nextSeg.streetID){
            distance += findStreetSegmentLength(streetSegs[i]);
        }

        // check for turns by checking change in streetID, if change in streetId, then write instruction
        if (currSeg.streetID != nextSeg.streetID){
            // check for direction change
            int direction = findLeftorRight(currSeg, nextSeg);

            // displays distance travelled on a straight segment
            distance += findStreetSegmentLength(streetSegs[i]); // add current street length
            if (getStreetName(currSeg.streetID) != unknownRoad) {
                // different messages displayed onto dialog box
                message_label += std::to_string(numberOfDirections) + ". ";

                if (numberOfDirections == 1)
                    message_label += "Travel straight on " + getStreetName(currSeg.streetID) + " for ";
                else 
                    message_label += "Continue straight on " + getStreetName(currSeg.streetID) + " for ";
                // displays km instead of m if > 1000m
                if (distance < 1000)
                    message_label += std::to_string(distance) + " m ";
                if (distance >= 1000){
                    // convert number to one decimal place
                    float display = (distance/1000.0);
                    float distance2 = floorf(display * 10)/ 10;

                    // set precision choses how many digits to display and typecast to string
                    std::stringstream stream;
                    if (distance < 10) // display 2 digits
                        stream.precision(2);
                    if (distance > 9 && distance < 100) // display 3 digits
                        stream.precision(3);
                    if (distance > 99) // display 4 digits
                        stream.precision(4);
                    stream << distance2;
                    std::string str = stream.str();
                    message_label += str + " km ";
                }
                if (getStreetName(nextSeg.streetID) != unknownRoad)
                    message_label += "towards " + getStreetName(nextSeg.streetID) + "\n";
                else
                    message_label += "\n";
            
                numberOfDirections ++; // increment counter because given instruction above
            }   

            if (getStreetName(nextSeg.streetID) != unknownRoad ) {
                // different messages displayed onto dialog box
                message_label += std::to_string(numberOfDirections) + ". ";

                if (direction == 0) // no turn but change in streetID
                    message_label += "Continue straight on " + getStreetName(nextSeg.streetID) + "\n";
                if (direction == 1) // turn right
                    message_label += "Turn right onto " + getStreetName(nextSeg.streetID) + "\n";
                if (direction == -1) // turn left
                    message_label += "Turn left onto " + getStreetName(nextSeg.streetID) + "\n";
                
                numberOfDirections ++; // increment counter again after instruction
            }
                distance = 0; // reset distance to 0 to count distance of next segment
        }
        
        // checks last segment and also if you start and end on the same street
        if (i == (streetSegs.size() - 2)) { // last instruction
    
            // displays distance travelled on a straight segment
            distance += findStreetSegmentLength(streetSegs[i + 1]); // add current street length
            
            if (getStreetName(currSeg.streetID) != unknownRoad) {
                // different messages displayed onto dialog box
                message_label += std::to_string(numberOfDirections) + ". ";
                
                if (numberOfDirections == 1)
                    message_label += "Travel straight on " + getStreetName(currSeg.streetID) + " for ";
                else
                    message_label += "Continue straight on " + getStreetName(currSeg.streetID) + " for ";
                
                // displays km instead of m if > 1000m
                if (distance < 1000)
                    message_label += std::to_string(distance) + " m \n";
                if (distance >= 1000){
                    // convert number to one decimal place
                    float display = (distance/1000.0);
                    float distance2 = floorf(display * 10)/ 10;

                    // set precision choses how many digits to display and typecast to string
                    std::stringstream stream;
                    if (distance < 10) // display 2 digits
                        stream.precision(2);
                    if (distance > 9 && distance < 100) // display 3 digits
                        stream.precision(3);
                    if (distance > 99) // display 4 digits
                        stream.precision(4);
                    stream << distance2;
                    std::string str = stream.str();

                    message_label += str + " km towards \n";
                }
            }
        }
    }

    // let the user know that they have arrived at the destination
    message_label += "\nYou have arrived at your destination, " + getIntersectionName(twoIntersections.second) + "\n";

    // display one dialog box if less than 30 directions
    if(streetSegs.size() < 30){
        main_label = gtk_label_new(message_label.c_str());
        gtk_container_add(GTK_CONTAINER(main_content_area), main_label);    
        gtk_widget_show_all(main_dialog);
    }
    // display second dialog box if more than 30 directions
    else{
        sub_label = gtk_label_new(message_label.c_str());
        gtk_container_add(GTK_CONTAINER(sub_content_area), sub_label);    
        gtk_widget_show_all(sub_dialog);
    }
}