#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); } }