"if" statement Looping through twice when using data driven pages - python

I have a script which is designed to place inset maps onto specific pages while exporting Data Driven Pages, the script is amalgamation of a friend's work and some of my own code from other projects.
The issue is the code exports pages 15 and 16 twice one with my inset maps and the other without and I can't figure out why.
I think it is something to do with the indentation within the Loop but I cant get it so it behaves in any other way. Any help would be appreciated!
import arcpy, os, time, datetime
from datetime import datetime
start_time = datetime.now()
PageNumber = "Page "
# Create an output directory variable i.e the location of your maps folder
outDir = r"C:\Users\support\Desktop\Python\Book of Reference"
# Create a new, empty pdf document in the specified output directory
# This will be your final product
finalpdf_filename = outDir + r"\FinalMapBook.pdf"
if os.path.exists(finalpdf_filename): # Check to see if file already exists, delete if it does
os.remove(finalpdf_filename)
finalPdf = arcpy.mapping.PDFDocumentCreate(finalpdf_filename)
# Create a Data Driven Pages object from the mxd you wish to export
mxdPath = r"C:\Users\support\Desktop\Python\Book Of Reference\Book_Of_Reference_20160526_Python_Test.mxd"
tempMap = arcpy.mapping.MapDocument(mxdPath)
tempDDP = tempMap.dataDrivenPages
# Create objects for the layout elements that will be moving, e.g., inset data frame, scale text
Page15 = arcpy.mapping.ListDataFrames(tempMap)[1]
Page16 = arcpy.mapping.ListDataFrames(tempMap)[2]
# Instead of exporting all pages at once, you will need to use a loop to export one at a time
# This allows you to check each index and execute code to add inset maps to the correct pages
for pgIndex in range(1, tempDDP.pageCount + 1, 1):
# Create a name for the pdf file you will create for each page
temp_filename = r"C:\Users\support\Desktop\Python\Book of Reference\Book of Reference" + \
str(pgIndex) + ".pdf"
if os.path.exists(temp_filename):
os.remove(temp_filename) #Removes pdf if it is already in the folder
# Code for setting up the inset map on the first page #
if (pgIndex == 15):
# Set position of inset map to place it on the page layout
Page15.elementPositionX = 20.1717
Page15.elementPositionY = 2.0382
# Set the desired size of the inset map for this page
Page15.elementHeight = 9.7337
Page15.elementWidth = 12.7115
# Set the desired extent for the inset map
Page15insetExtent = arcpy.Extent(518878,108329,519831,107599)
Page15insetExtent = Page15insetExtent
arcpy.RefreshActiveView()
tempDDP.exportToPDF(temp_filename, "RANGE", pgIndex)
finalPdf.appendPages(temp_filename)
Page15.elementPositionX = 50 #Move the Inset back off the page
arcpy.RefreshActiveView() #Refresh to ensure the Inset has been removed
print PageNumber + str(pgIndex)
if (pgIndex == 16):
# Set up inset map
Page16.elementPositionX = 2.1013
Page16.elementPositionY = 18.1914
Page16.elementHeight = 9.7337
Page16.elementWidth = 12.7115
Page16insetExtent = arcpy.Extent(520012, 107962, 521156,107086)
Page16insetExtent = Page16insetExtent
arcpy.RefreshActiveView()
print PageNumber + str(pgIndex)
tempDDP.exportToPDF(temp_filename, "RANGE", pgIndex)
finalPdf.appendPages(temp_filename)
print PageNumber + str(pgIndex)
Page16.elementPositionX = 50
arcpy.RefreshActiveView()
# Else Fuction takes care of the pages that dont have insets and just itterates through using the loop on line 28
else :
tempDDP.exportToPDF(temp_filename, "RANGE", pgIndex)
finalPdf.appendPages(temp_filename)
print PageNumber + str(pgIndex)
# Clean up
del tempMap
# Update the properties of the final pdf
finalPdf.updateDocProperties(pdf_open_view="USE_THUMBS",
pdf_layout="SINGLE_PAGE")
# Save your result
finalPdf.saveAndClose()
end_time = datetime.now()
print('Duration: {}'.format(end_time - start_time))

I believe your problem is that when the pgIndex is 15 it performs the export as intended. Then it checks if the pgIndex is 16. The pgIndex is not 16 so it drops into the else and re-exports without the inset maps. I would recommend changing the second if to an elif

Related

xlsxwriter image get overwritten when writing the files

I have a task of pulling images of items based on SKU and write them to an excel sheet. I can download the image fine and write it out. But issue is that when workbook.close() is called. xlsxwriter is only writing the last image. This is due to me saving space and overwriting the image after writing. Here is my write function:
def writeExcel(url, asin, imgLink, number):
if (url == -1): #incase image isn't able to be retrived
worksheet.write("A{}".format(number), asin)
worksheet.write("C{}".format(number), "N/A")
return
worksheet.write_string("A{}".format(number), asin)
imgPath = os.getcwd() + "/cache/img.jpg"
deleteCache() #remove the previous downloaded image to download the new one
getImage(imgLink) #download the image into ./cache/img.jpg
fixImage(imgPath) #fix the aspect ratio of image to fit into the cell
worksheet.insert_image("C{}".format(number), imgPath, {
"y_scale": 0.2,
"x_scale": 0.5,
"object_position": 1,
"url": url
})
It takes in the SKU of the item, and the image link. The calls getImage() which downloads it into ./cache/img.jpg. Then fixes the ratio with fixImage(). Finally it writes the image to the file.
This function is called in another function's for loop for each of the SKU.
Here is the function for reference.
def amazonSearch(asinList):
number = 0
for asin in asinList:
number += 1
if number % 25 == 0: #feedback to make sure it isn't stuck
print("Finished {}. Currently at {}".format(number, asin))
for region in regions:
req = requests.get(HOST.format(region, asin))
counter = 0
while (req.status_code == 503):
req = requests.get(HOST.format(region, asin))
time.sleep(1) #don't spam
counter += 1
if (counter >= 25):
break
if req.status_code == 200:
break
if (req.status_code != 200):
writeExcel(-1, asin, "", "")
continue
soup = bs(req.content, "html.parser")
imgTag = soup.find_all(id="landingImage")
imgLink = imgTag[0]["src"]
writeExcel(req.url, asin, imgLink, number)
After the script finishes. The file is written but the last SKU image will show up in all other SKUs. This is probably due to xlsxwriter only writing changes when workbook.close() is called.
My question is how can i fix that without having to save every single image and writing at the end? As the input file is pretty big (over 8k items). I have thought of closing and reopening sheet every time writeExcel() is called but that seems unfeasible. xlsxwriter overwrites every time so it can't be done.
insert_image only adds the image-path or url to a buffer. Later when closing/saving the workbook, the images are loaded from paths (all same in your case) and written to output.
You can fix by reading the image-binary and inserting using image_data:
image_file = open(filename, 'rb')
image_data = BytesIO(image_file.read())
image_file.close()
# Write the byte stream image to a cell. The filename must be specified
worksheet.insert_image('B8', filename, {'image_data': image_data})
Note: In this case, when image_data is present, the file at path/URL of argument filename does not need to exist. So you can treat filename argument rather as identifier or URI.
Since you are reading from the same cached file, your filename passed to insert_image as argument can be made unique by using some distinctive attribute like:
asin
url
For example:
filename_to_insert = asin + filename
or filename_to_insert = url
See:
Example: Inserting images from a URL or byte stream into a worksheet — XlsxWriter Documentation

Python-PPTX : Data Label Positions not working for Doughnut Chart

I have a Chart Placeholder, into which I have inserted a chart of chart_type 'DOUGHNUT'. I've added data labels to it and want to change their positions. For some reason, the method given in the documentation has no effect on my chart.
Here is my code, please help if I'm doing something wrong -
from pptx import Presentation
from pptx.chart.data import ChartData
from pptx.enum.chart import XL_CHART_TYPE, XL_LABEL_POSITION, XL_DATA_LABEL_POSITION, XL_TICK_MARK, XL_TICK_LABEL_POSITION
chart_data = ChartData()
chart_data.add_series('', tuple(input_chart_data[x] for x in input_chart_data))
graphic_frame = content_placeholder.insert_chart(XL_CHART_TYPE.DOUGHNUT, chart_data)
chart = graphic_frame.chart
chart.has_legend = False
#Adding Data-Labels with custom text
chart.plots[0].has_data_labels = True
data_labels = chart.plots[0].data_labels
i = 0
series = chart.series[0]
for point in series.points:
fill = point.format.fill
fill.solid()
fill.fore_color.rgb = RGBColor(<color_code>)
point.data_label.has_text_frame = True
#Assigning custom text for data label associated with each data-point
point.data_label.text_frame.text = str(chart_data.categories[i].label) + "\n" + str(float(chart.series[0].values[i])) + "%"
for run in point.data_label.text_frame.paragraphs[0].runs:
run.font.size = Pt(10)
i+=1
data_labels.position = XL_LABEL_POSITION.OUTSIDE_END
PowerPoint is finicky about where you place certain chart attributes and feels free to ignore them when it wants (although it does so consistently).
A quick option worth trying is to set the value individually, point-by-point in the series. So something like:
for point in series.points:
point.data_label.position = XL_LABEL_POSITION.OUTSIDE_END
The most reliable method is to start by producing the effect you want by hand, using PowerPoint itself on an example chart, then inspecting the XML PowerPoint produces in the saved file, perhaps using opc-diag. Once you've identified what XML produces the desired effect (or discovered PowerPoint won't let you do it), then you can proceed to working out how to get the XML generated by python-pptx. That might make a good second question if you're able to get that far.
I made it work by writing the below code.
def apply_data_labels(self, chart):
plot = chart.plots[0]
plot.has_data_labels = True
for series in plot.series:
values = series.values
counter = 0
for point in series.points:
data_label = point.data_label
data_label.has_text_frame = True
data_label.text_frame.text = str(values[counter])
counter = counter + 1
the cause of error is setting the label position. no matter what you set it asks to repair the PPT. will have to drill down more to see why is it so.
Also to save some more time the formatting doesn't works(font color, size)
If anybody has any leads then please help.
To add on Vibhanshu's response, I could get the formatting (font type, font color, size etc) to work using the following code:
for idx, point in enumerate(chart.series[0].points):
# set position
point.data_label.position = XL_LABEL_POSITION.OUTSIDE_END
# set text
point.data_label.has_text_frame = True
point.data_label.text_frame.text = "This is an example"
# set formatting
for paragraph_idx, paragraph in enumerate(point.data_label.text_frame.paragraphs):
paragraph.line_spacing = 0.6 # set paragraph line spacing
for run in paragraph.runs:
run.font.size = Pt(30) #set font size
run.font.name = 'Poppins Medium' #set font name
run.font.color.rgb = RGBColor.from_string("FF0000") #set font color

dropping an image with win32com (MS WORD)

I am trying to create a series of name badges and rather than doing it all by hand I'm trying to do it via python. I am using the win32com.client approach to create a table in msword to hold each name badge however the images I am inserting into each cell are pushed up against the top of the cell whereas I want them moved down a bit (Image is oversized I know but that can be dealt with later).
As you can see the image is write against the top of the border, I want it pushing down, I have tried adding newlines before (demonstrated below) but this seems to have had no effect. This is my loop for generating the badges.
for i in range(10):
cell_col = i % cols + 1
cell_row = i / cols + 1
cell_range = table.Cell(cell_row, cell_col).Range
cell_range.ParagraphFormat.SpaceBefore = 0
cell_range.ParagraphFormat.SpaceAfter = 3
table.Cell(cell_row, cell_col).Range.InsertBefore('\n')
cell_range.InlineShapes.AddPicture(os.path.join(os.path.abspath("."), filename))
table.Cell(cell_row, cell_col).Range.InsertAfter('\n'+hold[i])
table.Cell(cell_row, cell_col).Height = 150
table.Cell(cell_row, cell_col).Width = 250

Trouble with creaing custom tool in Arcmap

I am trying to create my own tool to use in ArcMap but keep running into a problem. I want to create a buffer (which I can do) and then clip the points that fall within the buffer. The problem I run into is that I cannot figure out how to use the buffer as the input feature for the clip section of my tool.
import arcpy
import os
from arcpyimmport env
env.workspace = "C:/LabData"
arcpy.env.overwriteOutput = True
In_lake = arcpy.GetParameterAsText(0)
Out_Buff = arcpy.GetParameterAsText(1)
Buffer_Distance = arcpy.GetParameterAstext(2)
in_cities = arcpy.GetParameterAsText(3)
cliped_cities = GetParameterAsText(4)
New_Table = arcpy.GetParameterAsText(5)
Join_Input = arcpy.GetParameteAsText(6)
# step 1 create a buffer around the lakes
arcpy.Buffer_analysis(In_Lake, Out_Buff, Buffer_Distance)
# Step 2 Clip all cities that fall within the buffer
arcpy.Clip_analysis( in_cities,out_Buff, clipped_cities)
# Step 3
arcpy.Statistics_analysis(clipped_cities, New_Table, statistics_fields,\
'Population SUM', 'CNTRY_NAME')
# Step 5
arcpy.AddField_management (New_Table, 'Country', 'TEXT')
]1
Check carefully that your variable names match -- Python and ArcPy are case sensitive.
In_Lake = arcpy.GetParameterAsText(0) ## was In_lake
Out_Buff = arcpy.GetParameterAsText(1)
Buffer_Distance = arcpy.GetParameterAstext(2)
in_cities = arcpy.GetParameterAsText(3)
clipped_cities = GetParameterAsText(4) ## was cliped_cities
New_Table = arcpy.GetParameterAsText(5)
Join_Input = arcpy.GetParameteAsText(6)
# step 1 create a buffer around the lakes
arcpy.Buffer_analysis(In_Lake, Out_Buff, Buffer_Distance)
# Step 2 Clip all cities that fall within the buffer
arcpy.Clip_analysis(in_cities, Out_Buff, clipped_cities) ## was out_Buff
Unless you want to keep the lake buffer, it doesn't necessarily need to be an input parameter specified by the user. Consider instead using the in_memory workspace -- just be aware any data in it will be deleted once the tool execution is completed.
Out_Buff = r'in_memory\lakeBuffer'
A similar strategy can be used for any intermediate feature class or table that you don't really care about. However, it's sometimes useful to have those intermediate results around to verify that your tool is doing what you expect at every step.

Appending multiple raster properties to a comma-delimited table

First-time post and python newb who has exhausted all other options. I am interested in appending selected raster properties (using the arcpy.GetRasterProperties_management(input_raster, "property_type") function) to a comma-delimited table, but am having trouble figuring out how to do this for multiple results. As an abridged example (of my actual script), I have created two 'for' loops; one for each raster property I am interested in outputting (i.e. Cell Size X, Cell Size Y). My list of rasters include S01Clip_30m through S05Clip_30m. My goal is to create a .txt file that should look something like this:
RasterName, CellSizeX, CellSizeY
S01Clip_30m, 88.9372, 88.9375
S02Clip_30m, 88.9374, 88.9371
The code I have so far is below (with some uncertain, botched syntax at the bottom). When I run it, I get this result:
S05Clip_30m, 88.9374
(last raster in the list, CellSizeY)
I appreciate any help you can provide on the crucial bottom code block.
import arcpy
from arcpy import env
env.workspace = ('C:\\StudyAreas\\Aggregates.gdb')
InFolder = ('C:\\dre\\python\\tables')
OutputFile = open(InFolder + '\\' + 'RasterProps.txt', 'a')
rlist = arcpy.ListRasters('*','*')
for grid in rlist:
if grid[-8:] == "Clip_30m":
result = arcpy.GetRasterProperties_management(grid,'CELLSIZEX')
CellSizeX = result.getOutput(0)
for grid in rlist:
if grid[-8:] == "Clip_30m":
result = arcpy.GetRasterProperties_management(grid,'CELLSIZEY')
CellSizeY = result.getOutput(0)
> I know the syntax below is incorrect, but I know there are *some* elements that
> should be included based on other example scripts that I have...
> if result.getOutput(0) == CellSizeX:
> coltype = CellSizeX
> elif result.getOutput(0) == CellSizeY:
> coltype = CellSizeY
> r = ''.join(grid)
> colname = r[0:]
> OutputFile.writelines(colname+','+coltype+'\n')
After receiving help from another Q&A forum on my script, I am now providing the answer to my own GIS-related question to close this thread (and move to gis.stackexchange :) - thanks to L.Yip's comment). Here is the final corrected script which outputs my two raster properties (Cell Size in X-direction, Cell Size in Y-direction) for a list of rasters into a .txt file:
import arcpy
from arcpy import env
env.workspace = ('C:\\StudyAreas\\Aggregates.gdb')
InFolder = ('C:\\dre\\python\\tables')
OutputFile = open(InFolder + '\\' + 'RasterProps.txt', 'a')
rlist = arcpy.ListRasters('*','*')
for grid in rlist:
if grid[-8:] == "Clip_30m":
resultX = arcpy.GetRasterProperties_management(grid,'CELLSIZEX')
CellSizeX = resultX.getOutput(0)
resultY = arcpy.GetRasterProperties_management(grid,'CELLSIZEY')
CellSizeY = resultY.getOutput(0)
OutputFile.write(grid + ',' + str(CellSizeX) + ',' + str(CellSizeY) + '\n')
OutputFile.close()
My results after running the script:
S01Clip_30m,88.937158083333,88.9371580833333
S02Clip_30m,88.937158083333,88.937158083333
S03Clip_30m,88.9371580833371,88.9371580833333
S04Clip_30m,88.9371580833308,88.937158083333
S05Clip_30m,88.9371580833349,88.937158083333
Thanks!

Categories