def post(self): # make tiles in zip file and write
        #print self.request.arguments() # should be the same as given to preflight
        self.response.headers['X-Content-Type-Options'] = 'nosniff' # Prevent browsers from MIME-sniffing the content-type:
        self.response.headers["X-Frame-Options"] = "SAMEORIGIN"   # prevent clickjacking
        self.response.out.write('<html><body>')
        self.response.out.write('<h2>Processing finished:</h2>')

        # debug: print/log all args and then values
        args = {} # put arg name and value in a dict as key:value
        for k in ("DEM_name", "trlat", "trlon", "bllat", "bllon", "printres", "ntilesx", "ntilesy", "tilewidth",
                  "basethick", "zscale", "fileformat"):
            v = self.request.get(k) # key = name of arg
            args[k] = v # value
            if k not in ["DEM_name", "fileformat"]: args[k] = float(args[k]) # floatify non-string args
            #print k, args[k]
            self.response.out.write("%s = %s <br>" % (k, str(args[k])))
            logging.info("%s = %s" % (k, str(args[k])))

        args["CPU_cores_to_use"] = NUM_CORES

        # name of zip file is time since 2000 in 0.01 seconds
        myname = str(int((datetime.now()-datetime(2000,1,1)).total_seconds() * 1000))
        fname = tmp_folder + os.sep + myname + ".zip"  # create filename for zip and put in tmp folder
        args["zip_file_name"] = fname

        # if this number of cells to process is exceeded, use a temp file instead of
        args["max_cells_for_memory_only"] = MAX_CELLS


        try:
            # create zip and write to tmp
            total_unzipped_size = TouchTerrainEarthEngine.get_zipped_tiles(**args) # all args are in a dict
        except Exception, e:
            logging.error(e)
            print(e)
            self.response.out.write(e)
            return
def main():
    # write an example json file, in case it gets deleted ...
    with open('example_config.json', 'w+') as fp:
        json.dump(args, fp, indent=0,
                  sort_keys=True)  # indent = 0: newline after each comma
    print(
        'Wrote example_config.json with default values, you can use it as a template but make sure to rename it!'
    )

    import sys, os
    from os.path import abspath, dirname

    # parse args
    if len(sys.argv) > 1:  # sys.argv are the CLI args
        json_fname = sys.argv[1]
        try:
            fp = open(json_fname, "rU")
        except Exception as e:
            sys.exit("Error: can't find " + json_fname + ": " + str(e))

        file_content = fp.read()
        try:
            json_args = json.loads(file_content)
        except Exception as e:
            sys.exit("Error: can't json parse " + json_fname + ": " + str(e))

        print("reading", json_fname)

        for k in list(args.keys()):
            try:
                args[k] = json_args[
                    k]  # try to find a value for k in json config file
                #print k, args[k]
            except:
                print(
                    "info:", k,
                    "has missing or invalid value, using defaults where possible"
                )  # no match? no problem, just keep the default value
                #print "%s = %s" % (k, str(args[k]))
    else:
        # no JSON config file given, setting config values in code
        # you can comment out lines for which you don't want to overwrite the default settings
        overwrite_args = {
            "DEM_name":
            'USGS/NED',  # DEM_name:    name of DEM source used in Google Earth Engine
            # for all valid sources, see DEM_sources in TouchTerrainEarthEngine.py
            "trlat": 44.69741706507476,  # lat/lon of top right corner
            "trlon": -107.97962089843747,
            "bllat": 44.50185267072875,  # lat/lon of bottom left corner
            "bllon": -108.25427910156247,
            "importedDEM":
            None,  # if not None, the raster file to use as DEM instead of using GEE (null in JSON)
            "printres":
            0.5,  # resolution (horizontal) of 3D printer (= size of one pixel) in mm
            "ntilesx": 1,  # number of tiles in x and y
            "ntilesy": 1,
            "tilewidth":
            80,  # width of each tile in mm (<- !!!!!), tile height is calculated
            "basethick": 1,  # thickness (in mm) of printed base
            "zscale": 1.0,  # elevation (vertical) scaling
            "fileformat":
            "STLb",  # format of 3D model files: "obj" wavefront obj (ascii),"STLa" ascii STL or "STLb" binary STL
            "tile_centered":
            False,  # True-> all tiles are centered around 0/0, False, all tiles "fit together"
            "zip_file_name":
            "terrain",  # base name of zipfile, .zip will be added
            "CPU_cores_to_use":
            0,  # 0 means all cores, None (null in JSON!) => don't use multiprocessing
            "max_cells_for_memory_only": 1000 *
            1000,  # if raster is bigger, use temp_files instead of memory
            "no_bottom": False,  # omit bottom triangles?
            #"rot_degs": 0, # rotate by degrees ccw  # CH disabled for now
            "bottom_image":
            None,  # 1 band greyscale image used for bottom relief
            "ignore_leq":
            None,  # set values <= this to NaN, so they are ignored
            "unprojected":
            False,  # don't project to UTM, only usefull when using GEE for DEM rasters
            "only":
            None,  # list of tile index [x,y] with is the only tile to be processed. None means process all tiles (index is 1 based)
        }

        # overwrite config settings in args
        for k in overwrite_args:
            args[k] = overwrite_args[k]

    # print out current args
    print("\nUsing these config values:")
    for k in sorted(args.keys()):
        print("%s = %s" % (k, str(args[k])))

    # No DEM file given, use Google Earth Engine
    if args["importedDEM"] == None:
        # initialize ee - needs a google earth engine account! See TouchTerrain_standalone_installation.pdf
        try:
            import ee
        except Exception as e:
            print("Google Earth Engine module not installed",
                  e,
                  file=sys.stderr)

        # try both ways of authenticating
        try:
            ee.Initialize()  # uses .config/earthengine/credentials
        except Exception as e:
            print("EE init() error (with .config/earthengine/credentials)",
                  e,
                  file=sys.stderr)

            try:
                # try authenticating with a .pem file
                from common import config  # sets location of .pem file, config.py must be in this folder
                from oauth2client.service_account import ServiceAccountCredentials
                from ee import oauth
                credentials = ServiceAccountCredentials.from_p12_keyfile(
                    config.EE_ACCOUNT,
                    config.EE_PRIVATE_KEY_FILE,
                    scopes=oauth.SCOPES)
                ee.Initialize(credentials, config.EE_URL)
            except Exception as e:
                print("EE init() error (with config.py and .pem file)",
                      e,
                      file=sys.stderr)

    else:
        args["importedDEM"] = abspath(args["importedDEM"])

    # TODO: should change TouchTerrainEarthEngine.py to TouchTerrain.py as it now also deals with file DEMs
    from common import TouchTerrainEarthEngine as TouchTerrain

    totalsize, full_zip_file_name = TouchTerrain.get_zipped_tiles(
        **args)  # all args are in a dict
    print("\nCreated zip file", full_zip_file_name, "%.2f" % totalsize, "Mb")

    # Optional: unzip the zip file into the current folder
    if 1:  # set this to 0 if you don't want the zip file tp be unzippeed
        #import os.path
        #folder, file = os.path.splitext(full_zip_file_name) # tmp folder
        folder = os.getcwd() + os.sep + args[
            "zip_file_name"]  # new stl folder in current folder

        # unzip the zipfile into the folder it's already in
        import zipfile
        zip_ref = zipfile.ZipFile(full_zip_file_name, 'r')
        zip_ref.extractall(folder)
        zip_ref.close()
        print("unzipped file inside", full_zip_file_name, "into", folder)
    '''
    def post(self):  # make tiles in zip file and write
        #print self.request.arguments() # should be the same as given to preflight
        self.response.headers[
            'X-Content-Type-Options'] = 'nosniff'  # Prevent browsers from MIME-sniffing the content-type:
        self.response.headers[
            "X-Frame-Options"] = "SAMEORIGIN"  # prevent clickjacking
        self.response.out.write('<html><body>')
        self.response.out.write('<h2>Processing finished:</h2>')

        # debug: print/log all args and then values
        args = {}  # put arg name and value in a dict as key:value
        for k in ("DEM_name", "trlat", "trlon", "bllat", "bllon", "printres",
                  "ntilesx", "ntilesy", "tilewidth", "basethick", "zscale",
                  "fileformat"):
            v = self.request.get(k)  # key = name of arg
            args[k] = v  # value
            if k not in ["DEM_name", "fileformat"]:
                args[k] = float(args[k])  # floatify non-string args
            if k[:4] == "tile":
                args[k] = args[
                    k] * 10  # multiply tilewidth/height by 10 to get from cm to mm
            #print k, args[k]
            self.response.out.write("%s = %s <br>" % (k, str(args[k])))
            logging.info("%s = %s" % (k, str(args[k])))

        # name of file is seconds since 2000
        myname = str(
            int((datetime.now() - datetime(2000, 1, 1)).total_seconds() *
                1000))

        # create zip and write to tmp
        str_buf = TouchTerrainEarthEngine.get_zipped_tiles(
            **args)  # all args are in a dict
        fname = myname + ".zip"  # create filename for zipped
        logging.info("About to write: " + tmp_folder + os.sep + fname)
        fname = tmp_folder + os.sep + fname  # put in tmp folder
        if SERVER_TYPE == "GAE_devserver":
            f = stubs.FakeFile(
                fname, "wb+"
            )  # make a fake Fakefile instance (with devserver's write restriction disabled)
        else:
            f = open(fname, "wb+")  # write to folder
        f.write(str_buf)
        f.close()
        logging.info("finished writing %s" % (myname))

        #str_buf = TouchTerrain.get_zipped_tiles(**args)
        #str_buf = TouchTerrain.get_zipped_tiles("USGS/NED", ntilesx=2, ntilesy=2, **args)
        #self.response.headers['Content-Type'] = 'text/zip'
        #self.response.write(str_buf)

        self.response.out.write(
            '<br><form action="tmp/%s.zip" method="GET" enctype="multipart/form-data">'
            % (myname))
        self.response.out.write(
            '<input type="submit" value="Download zip File " title="">   (will be deleted in 24 hrs)</form>'
        )
        #self.response.out.write('<form action="/" method="GET" enctype="multipart/form-data">')
        #self.response.out.write('<input type="submit" value="Go back to selection map"> </form>')
        self.response.out.write(
            "<br>To return to the selection map, click the back button in your browser twice"
        )
        self.response.out.write(
            """<br>After downloading you can preview a STL/OBJ file at <a href="http://www.viewstl.com/" target="_blank"> www.viewstl.com ) </a>  (limit: 35 Mb)"""
        )
    def preflight_generator():

        # create html string
        html = '<html>'

        html += GA_script  # <head> with script that inits GA with my tracking id and calls send pageview

        # onload event will only be triggered once </body> is given
        html += '''<body onerror="document.getElementById('error').innerHTML='Error (non-python), possibly the server timed out ...'"\n onload="document.getElementById('gif').style.display='none'; document.getElementById('working').innerHTML='Processing finished'">\n'''
        html += '<h2 id="working" >Processing terrain data into 3D print file(s), please be patient.<br>\n'
        html += 'Once the animation stops, you can preview and download your file.</h2>\n'
        yield html  # this effectively prints html into the browser but doesn't block, so we can keep going and append more html later ...

        #
        #  print/log all args and their values
        #

        # put all agrs we got from the browser in a  dict as key:value
        args = request.form.to_dict()

        # list of the subset of args needed for processing
        key_list = ("DEM_name", "trlat", "trlon", "bllat", "bllon", "printres",
                    "ntilesx", "ntilesy", "tilewidth", "basethick", "zscale",
                    "fileformat")

        for k in key_list:

            # float-ify some ags
            if k in [
                    "trlat", "trlon", "bllat", "bllon", "printres",
                    "tilewidth", "basethick", "zscale"
            ]:
                args[k] = float(args[k])

            # int-ify some args
            if k in ["ntilesx", "ntilesy"]:
                args[k] = int(args[k])

        # decode any extra (manual) args and put them in the args dict as
        # separate args as the are needed in that form for processing
        # Note: the type of each arg is decided by  json.loads(), so 1.0 will be a float, etc.
        manual = args.get("manual", None)
        extra_args = {}
        if manual != None:

            JSON_str = "{ " + manual + "}"
            try:
                extra_args = json.loads(JSON_str)
            except Exception as e:
                s = "JSON decode Error for manual: " + manual + "   " + str(e)
                logging.warning(s)
                print(e)
                yield "Warning: " + s + "<br>"
            else:
                for k in extra_args:
                    args[k] = extra_args[k]  # append/overwrite
                    # TODO: validate

        # log and show args in browser
        html = '<br>'
        for k in key_list:
            html += "%s = %s <br>" % (k, str(args[k]))
            logging.info("%s = %s" % (k, str(args[k])))
        html += "<br>"
        for k in extra_args:
            html += "%s = %s <br>" % (k, str(args[k]))
            logging.info("%s = %s" % (k, str(args[k])))
        html += "<br>"
        yield html

        #
        # bail out if the raster would be too large
        #
        width = args["tilewidth"]
        bllon = args["bllon"]
        trlon = args["trlon"]
        bllat = args["bllat"]
        trlat = args["trlat"]
        dlon = 180 - abs(abs(bllon - trlon) - 180)  # width in degrees
        dlat = 180 - abs(abs(bllat - trlat) - 180)  # height in degrees
        center_lat = bllat + abs((bllat - trlat) / 2.0)
        latitude_in_m, longitude_in_m = arcDegr_in_meter(center_lat)
        num_total_tiles = args["ntilesx"] * args["ntilesy"]
        pr = args["printres"]

        # if we have "only" set, divide load by number of tiles
        div_by = 1
        if extra_args.get("only") != None:
            div_by = float(num_total_tiles)

        # for geotiffs only, set a much higher limit b/c we don't do any processing,
        # just d/l the GEE geotiff and zip it
        if args["fileformat"] == "GeoTiff":
            global MAX_CELLS_PERMITED  # thanks Nick!
            MAX_CELLS_PERMITED *= 100

        # pr <= 0 means: use source
        if pr > 0:  # print res given by user (width and height are in mm)
            height = width * (dlat / float(dlon))
            pix_per_tile = (width / float(pr)) * (height / float(pr))
            tot_pix = int((pix_per_tile * num_total_tiles) /
                          div_by)  # total pixels to print
            print("total requested pixels to print",
                  tot_pix,
                  ", max is",
                  MAX_CELLS_PERMITED,
                  file=sys.stderr)
        else:
            # estimates the total number of cells from area and arc sec resolution of source
            # this is done for the entire area, so number of cell is irrelevant
            DEM_name = args["DEM_name"]
            cell_width_arcsecs = {
                """USGS/NED""": 1 / 9.0,
                """USGS/GMTED2010""": 7.5,
                """NOAA/NGDC/ETOPO1""": 30,
                """USGS/SRTMGL1_003""": 1
            }  # in arcseconds!
            cwas = float(cell_width_arcsecs[DEM_name])
            tot_pix = int(
                (((dlon * 3600) / cwas) * ((dlat * 3600) / cwas)) / div_by)
            print("total requested pixels to print at a source resolution of",
                  round(cwas, 2),
                  "arc secs is ",
                  tot_pix,
                  ", max is",
                  MAX_CELLS_PERMITED,
                  file=sys.stderr)

        if tot_pix > MAX_CELLS_PERMITED:
            html = "Your requested job is too large! Please reduce the area (red box) or lower the print resolution<br>"
            html += "<br>Current total number of Kilo pixels is " + str(
                round(tot_pix / 1000.0, 2))
            html += " but must be less than " + str(
                round(MAX_CELLS_PERMITED / 1000.0, 2))
            html += "<br><br>Hit Back on your browser to go back to the Main page and make adjustments ...\n"
            html += '</body></html>'
            yield html
            return "bailing out!"

        args["CPU_cores_to_use"] = NUM_CORES

        # check if we have a valid temp folder
        args["temp_folder"] = TMP_FOLDER
        print("temp_folder is set to", args["temp_folder"], file=sys.stderr)
        if not os.path.exists(args["temp_folder"]):
            s = "temp folder " + args["temp_folder"] + " does not exist!"
            print(s, file=sys.stderr)
            logging.error(s)
            html = '</body></html>Error:' + s
            yield html
            return "bailing out!"  # Cannot continue without proper temp folder

        # name of zip file is time since 2000 in 0.01 seconds
        fname = str(
            int((datetime.now() - datetime(2000, 1, 1)).total_seconds() *
                1000))
        args["zip_file_name"] = fname

        # if this number of cells to process is exceeded, use a temp file instead of memory only
        args["max_cells_for_memory_only"] = MAX_CELLS

        # show snazzy animate gif - set to style="display: none to hide once
        html = '<img src="static/processing.gif" id="gif" alt="processing animation" style="display: block;">\n'

        # add an empty paragraph for error messages during processing that come from JS
        html += '<p id="error"> </p>\n'
        yield html

        #
        # Create zip and write to tmp
        #
        try:
            totalsize, full_zip_file_name = TouchTerrainEarthEngine.get_zipped_tiles(
                **args)  # all args are in a dict
        except Exception as e:
            print("Error:", e, file=sys.stderr)
            html = '</body></html>' + "Error:," + str(e)
            yield html
            return "bailing out!"

        # if totalsize is negative, something went wrong, error message is in full_zip_file_name
        if totalsize < 0:
            print("Error:", full_zip_file_name, file=sys.stderr)
            html = '</body></html>' + "Error:," + str(full_zip_file_name)
            yield html
            return "bailing out!"

        else:
            html = ""

            # move zip from temp folder to static folder so flask can serve it (. is server root!)
            zip_file = fname + ".zip"
            try:
                os.rename(full_zip_file_name,
                          os.path.join(DOWNLOADS_FOLDER, zip_file))
            except Exception as e:
                print("Error:", e, file=sys.stderr)
                html = '</body></html>' + "Error:," + str(e)
                yield html
                return "bailing out!"

            zip_url = url_for("download", filename=zip_file)

            if args["fileformat"] in ("STLa", "STLb"):
                html += '<br><form action="' + url_for(
                    "preview", zip_file=zip_file
                ) + '" method="GET" enctype="multipart/form-data">'
                html += '  <input type="submit" value="Preview STL " '
                html += ''' onclick="ga('send', 'event', 'Preview', 'Click', 'preview', '0')" '''
                html += '   title=""> '
                html += 'Note: This uses WebGL for in-browser 3D rendering and may take a while to load for large models.<br>\n'
                html += 'You may not see anything for a while even after the progress bar is full!'
                html += '</form>\n'

            html += "Optional: tell us what you're using this model for<br>\n"
            html += '''<textarea autofocus form="dl" id="comment" cols="100" maxlength=150 rows="2"></textarea><br>\n'''

            html += '<br>\n<form id="dl" action="' + zip_url + '" method="GET" enctype="multipart/form-data">\n'
            html += '  <input type="submit" value="Download zip File " \n'
            #https://stackoverflow.com/questions/57499732/google-analytics-events-present-in-console-but-no-more-in-api-v4-results
            html += '''  onclick=onclick_for_dl();\n'''

            #html += '''  onclick="ga('send', 'event', 'Download', 'Click', 'from preview', '0');\n
            #ga('send', 'event', 'Comment1', 'Click', document.getElementById('comment') , 1);"\n '''
            #{
            #'dimension1': document.getElementById('comment').value,
            #'dimension2': 'Test for setting dimension2 from download button click'
            #'dimension03': 'Test for setting dimension03 from download button click'
            #},
            #1);" \n

            html += '   title="zip file contains a log file, the geotiff of the processed area and the 3D model file (stl/obj) for each tile">\n'
            html += "   Size: %.2f Mb   (All files will be deleted in 6 hrs.)<br>\n" % totalsize
            html += "   <br>To return to the selection map, click the back button in your browser once.\n"
            html += '</form>\n'

            html += '</body></html>'
            yield html


if len(sys.argv) > 1:  # sys.argv are the CLI args
    json_fname = sys.argv[1]
    try:
	fp = open(json_fname, "rU")
	json_args = json.load(fp)
    except:
	sys.exit("Error: config file", json_fname, "not found")
    print "reading", json_fname

    for k in args.keys():
	try:
	    args[k] = json_args[k]  # try to find a value for k in json config file
	except:
	    print "warning: ignored invalid option", k   # no match, no problem, just keep the default value
	print "%s = %s" % (k, str(args[k]))
else:
    print "no config file given, using defaults:"
    for k in args.keys():
	print "%s = %s" % (k, str(args[k]))

fname = args["zip_file_name"] + "_" + time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime()) + ".zip"
del args["zip_file_name"] # otherwise get_zipped_tiles complains about this argument

str_buf = TouchTerrain.get_zipped_tiles(**args) # all args are in a dict
with open(fname, 'wb+') as fp:
    fp.write(str_buf)
    print "finished writing %s" % (fname)