Пример #1
0
def listShipsPresent(tdb, cmdenv, station, results):
    """ Populate results with a list of ships present at the given station """
    cur = tdb.query(
        """
        SELECT ship_id
          FROM ShipVendor
         WHERE station_id = ?
    """, [station.ID])

    results.summary = ResultRow()
    results.summary.station = station
    ships = tdb.shipByID
    addShip = results.rows.append

    for (ship_id, ) in cur:
        ship = ships.get(ship_id, None)
        if ship:
            addShip(ResultRow(ship=ship))

    if cmdenv.nameSort:
        results.rows.sort(key=lambda row: row.ship.name())
    else:
        results.rows.sort(key=lambda row: row.ship.cost, reverse=True)

    return results
Пример #2
0
def run(results, cmdenv, tdb):
    from commands.commandenv import ResultRow

    calc = TradeCalc(tdb, cmdenv)

    lhs = cmdenv.startStation
    rhs = cmdenv.stopStation

    if lhs == rhs:
        raise CommandLineError("Must specify two different stations.")

    results.summary = ResultRow()
    results.summary.fromStation = lhs
    results.summary.toStation = rhs

    trades = calc.getTrades(lhs, rhs)
    if not trades:
        raise CommandLineError("No profitable trades {} -> {}".format(
            lhs.name(), rhs.name()))

    if cmdenv.detail > 1:
        tdb.getAverageSelling()
        tdb.getAverageBuying()

    for item in trades:
        results.rows.append(item)

    return results
Пример #3
0
def run(results, cmdenv, tdb):
    if cmdenv.lsFromStar and cmdenv.lsFromStar < 0:
        raise CommandLineError("Invalid (negative) --ls option")

    system, station = checkSystemAndStation(tdb, cmdenv)

    systemName = cmdenv.system
    stationName = cmdenv.station

    if cmdenv.add:
        result = addStation(tdb, cmdenv, system, stationName)
        return checkResultAndExportStations(tdb, cmdenv, result)
    elif cmdenv.update:
        result = updateStation(tdb, cmdenv, station)
        return checkResultAndExportStations(tdb, cmdenv, result)
    elif cmdenv.remove:
        result = removeStation(tdb, cmdenv, station)
        return checkResultAndExportStations(tdb, cmdenv, result)

    # Otherwise, it's just a query
    results.summary = ResultRow()
    results.summary.system = station.system
    results.summary.station = station

    avgSell = results.summary.avgSelling = tdb.getAverageSelling()
    avgBuy = results.summary.avgBuying = tdb.getAverageBuying()

    class ItemTrade(object):
        def __init__(self, ID, price, avgAgainst):
            self.ID, self.item = ID, tdb.itemByID[ID]
            self.price = int(price)
            self.avgTrade = avgAgainst.get(ID, 0)

    # Look up all selling and buying by the station
    selling, buying = [], []
    cur = tdb.query(
        """
        SELECT  item_id, demand_price, supply_price
          FROM  StationItem
         WHERE  station_id = ?
                AND (demand_price > 10 or supply_price > 10)
    """, [station.ID])
    for ID, demand_price, supply_price in cur:
        if demand_price > 10 and avgSell.get(ID, 0) > 10:
            buying.append(ItemTrade(ID, demand_price, avgSell))
        if supply_price > 10 and avgBuy.get(ID, 0) > 10:
            selling.append(ItemTrade(ID, supply_price, avgBuy))
    selling.sort(key=lambda item: item.price - item.avgTrade, )
    results.summary.selling = selling[:5]
    buying.sort(key=lambda item: item.avgTrade - item.price, )
    results.summary.buying = buying[:5]

    return results
Пример #4
0
def run(results, cmdenv, tdb):
    """
    Fetch all the data needed to display the results of a "rares"
    command. Does not actually print anything.

    Command execution is broken into two steps:
        1. cmd.run(results, cmdenv, tdb)
            Gather all the data required but generate no output,
        2. cmd.render(results, cmdenv, tdb)
            Print output to the user.

    This separation of concerns allows modularity; you can write
    a command that calls another command to fetch data for you
    and knowing it doesn't generate any output. Then you can
    process the data and return it and let the command parser
    decide when to turn it into output.

    It also opens a future door to commands that can present
    their data in a GUI as well as the command line by having
    a custom render() function.

    Parameters:
        results
            An object to be populated and returned
        cmdenv
            A CommandEnv object populated with the parameters
            for the command.
        tdb
            A TradeDB object to query against.

    Returns:
        None
            End execution without any output
        results
            Proceed to "render" with the output.
    """

    # Lookup the system we're currently in.
    start = cmdenv.nearSystem
    # Hoist the padSize parameter for convenience
    padSize = cmdenv.padSize
    # How far we're want to cast our net.
    maxLy = float(cmdenv.maxLyPer or 0.0)

    if cmdenv.illegal:
        wantIllegality = "Y"
    elif cmdenv.legal:
        wantIllegality = "N"
    else:
        wantIllegality = "YN?"

    awaySystems = set()
    if cmdenv.away or cmdenv.awayFrom:
        if not cmdenv.away or not cmdenv.awayFrom:
            raise CommandLineError("Invalid --away/--from usage. See --help")
        minAwayDist = cmdenv.away
        for sysName in cmdenv.awayFrom:
            system = tdb.lookupPlace(sysName).system
            awaySystems.add(system)

    # Start to build up the results data.
    results.summary = ResultRow()
    results.summary.near = start
    results.summary.ly = maxLy
    results.summary.awaySystems = awaySystems

    distCheckFn = start.distanceTo

    # Look through the rares list.
    for rare in tdb.rareItemByID.values():
        if not rare.illegal in wantIllegality:
            continue
        if padSize:  # do we care about pad size?
            if not rare.station.checkPadSize(padSize):
                continue
        rareSys = rare.station.system
        # Find the un-sqrt'd distance to the system.
        dist = distCheckFn(rareSys)
        if 0 < maxLy < dist:
            continue

        if awaySystems:
            awayCheck = rareSys.distanceTo
            if any(awayCheck(away) < minAwayDist for away in awaySystems):
                continue

        # Create a row for this item
        row = ResultRow()
        row.rare = rare
        row.dist = dist
        results.rows.append(row)

    # Was anything matched?
    if not results:
        print("No matches found.")
        return None

    if cmdenv.sortByPrice:
        results.rows.sort(key=lambda row: row.dist)
        results.rows.sort(key=lambda row: row.rare.costCr, reverse=True)
    else:
        results.rows.sort(key=lambda row: row.rare.costCr, reverse=True)
        results.rows.sort(key=lambda row: row.dist)

    if cmdenv.reverse:
        results.rows.reverse()

    limit = cmdenv.limit or 0
    if limit > 0:
        results.rows = results.rows[:limit]

    return results
Пример #5
0
def run(results, cmdenv, tdb):
    """
    Fetch all the data needed to display the results of a "rares"
    command. Does not actually print anything.

    Command execution is broken into two steps:
        1. cmd.run(results, cmdenv, tdb)
            Gather all the data required but generate no output,
        2. cmd.render(results, cmdenv, tdb)
            Print output to the user.

    This separation of concerns allows modularity; you can write
    a command that calls another command to fetch data for you
    and knowing it doesn't generate any output. Then you can
    process the data and return it and let the command parser
    decide when to turn it into output.

    It also opens a future door to commands that can present
    their data in a GUI as well as the command line by having
    a custom render() function.

    Parameters:
        results
            An object to be populated and returned
        cmdenv
            A CommandEnv object populated with the parameters
            for the command.
        tdb
            A TradeDB object to query against.

    Returns:
        None
            End execution without any output
        results
            Proceed to "render" with the output.
    """

    # Lookup the system we're currently in.
    start = cmdenv.nearSystem
    # Hoist the padSize, noPlanet and planetary parameter for convenience
    padSize = cmdenv.padSize
    noPlanet = cmdenv.noPlanet
    planetary = cmdenv.planetary
    # How far we're want to cast our net.
    maxLy = float(cmdenv.maxLyPer or 0.)

    if cmdenv.illegal:
        wantIllegality = 'Y'
    elif cmdenv.legal:
        wantIllegality = 'N'
    else:
        wantIllegality = 'YN?'

    awaySystems = set()
    if cmdenv.away or cmdenv.awayFrom:
        if not cmdenv.away or not cmdenv.awayFrom:
            raise CommandLineError("Invalid --away/--from usage. See --help")
        minAwayDist = cmdenv.away
        for sysName in cmdenv.awayFrom:
            system = tdb.lookupPlace(sysName).system
            awaySystems.add(system)

    # Start to build up the results data.
    results.summary = ResultRow()
    results.summary.near = start
    results.summary.ly = maxLy
    results.summary.awaySystems = awaySystems

    distCheckFn = start.distanceTo

    # Look through the rares list.
    for rare in tdb.rareItemByID.values():
        if not rare.illegal in wantIllegality:
            continue
        if padSize:  # do we care about pad size?
            if not rare.station.checkPadSize(padSize):
                continue
        if planetary:  # do we care about planetary?
            if not rare.station.checkPlanetary(planetary):
                continue
        if noPlanet and rare.station.planetary != 'N':
            continue
        rareSys = rare.station.system
        # Find the un-sqrt'd distance to the system.
        dist = distCheckFn(rareSys)
        if maxLy > 0. and dist > maxLy:
            continue

        if awaySystems:
            awayCheck = rareSys.distanceTo
            if any(awayCheck(away) < minAwayDist for away in awaySystems):
                continue

        # Create a row for this item
        row = ResultRow()
        row.rare = rare
        row.dist = dist
        results.rows.append(row)

    # Was anything matched?
    if not results:
        print("No matches found.")
        return None

    if cmdenv.sortByPrice:
        results.rows.sort(key=lambda row: row.dist)
        results.rows.sort(key=lambda row: row.rare.costCr, reverse=True)
    else:
        results.rows.sort(key=lambda row: row.rare.costCr, reverse=True)
        results.rows.sort(key=lambda row: row.dist)

    if cmdenv.reverse:
        results.rows.reverse()

    limit = cmdenv.limit or 0
    if limit > 0:
        results.rows = results.rows[:limit]

    return results
Пример #6
0
def run(results, cmdenv, tdb):
    origin = cmdenv.startStation
    if not origin.itemCount:
        raise CommandLineError(
            "No trade data available for {}".format(origin.name())
        )

    buying, selling = cmdenv.buying, cmdenv.selling

    results.summary = ResultRow()
    results.summary.origin = origin
    results.summary.buying = cmdenv.buying
    results.summary.selling = cmdenv.selling

    tdb.getAverageSelling()
    tdb.getAverageBuying()
    cur = tdb.query("""
        SELECT  item_id,
                demand_price, demand_units, demand_level,
                supply_price, supply_units, supply_level,
                JULIANDAY('now') - JULIANDAY(modified)
          FROM  StationItem
         WHERE  station_id = ?
    """, [origin.ID])

    for row in cur:
        it = iter(row)
        item = tdb.itemByID[next(it)]
        row = ResultRow()
        row.item = item
        row.buyCr = int(next(it) or 0)
        row.avgBuy = tdb.avgBuying.get(item.ID, 0)
        units, level = int(next(it) or 0), int(next(it) or 0)
        row.buyUnits = units
        row.buyLevel = level
        row.demand = render_units(units, level)
        if not selling:
            hasBuy = (row.buyCr or units or level)
        else:
            hasBuy = False
        row.sellCr = int(next(it) or 0)
        row.avgSell = tdb.avgSelling.get(item.ID, 0)
        units, level = int(next(it) or 0), int(next(it) or 0)
        row.sellUnits = units
        row.sellLevel = level
        row.supply = render_units(units, level)
        if not buying:
            hasSell = (row.sellCr or units or level)
        else:
            hasSell = False
        row.age = float(next(it) or 0)

        if hasBuy or hasSell:
            results.rows.append(row)

    if not results.rows:
        raise CommandLineError("No items found")

    results.rows.sort(key=lambda row: row.item.dbname)
    results.rows.sort(key=lambda row: row.item.category.dbname)

    return results
Пример #7
0
def run(results, cmdenv, tdb):
    from commands.commandenv import ResultRow

    if cmdenv.lt and cmdenv.gt:
        if cmdenv.lt <= cmdenv.gt:
            raise CommandLineError("--gt must be lower than --lt")

    item = tdb.lookupItem(cmdenv.item)
    cmdenv.DEBUG0("Looking up item {} (#{})", item.name(), item.ID)

    avoidSystems = {s for s in cmdenv.avoidPlaces if isinstance(s, System)}
    avoidStations = {s for s in cmdenv.avoidPlaces if isinstance(s, Station)}

    results.summary = ResultRow()
    results.summary.item = item
    results.summary.avoidSystems = avoidSystems
    results.summary.avoidStations = avoidStations

    if cmdenv.detail:
        avgPrice = tdb.query("""
            SELECT AVG(si.demand_price)
              FROM StationItem AS si
             WHERE si.item_id = ? AND si.demand_price > 0
        """, [item.ID]).fetchone()[0]
        results.summary.avg = int(avgPrice)

    # Constraints
    tables = "StationItem AS si"
    constraints = [
        "(item_id = {} AND demand_price > 0)".format(item.ID),
    ]
    columns = [
        'si.station_id',
        'si.demand_price',
        'si.demand_units',
    ]
    bindValues = []

    if cmdenv.demand:
        constraints.append("(demand_units >= ?)")
        bindValues.append(cmdenv.demand)

    if cmdenv.lt:
        constraints.append("(demand_price < ?)")
        bindValues.append(cmdenv.lt)
    if cmdenv.gt:
        constraints.append("(demand_price > ?)")
        bindValues.append(cmdenv.gt)

    nearSystem = cmdenv.nearSystem
    if nearSystem:
        maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy
        results.summary.near = nearSystem
        results.summary.ly = maxLy
        distanceFn = nearSystem.distanceTo
    else:
        distanceFn = None

    whereClause = ' AND '.join(constraints)
    stmt = """SELECT DISTINCT {columns} FROM {tables} WHERE {where}""".format(
        columns=','.join(columns),
        tables=tables,
        where=whereClause
    )
    cmdenv.DEBUG0('SQL: {}', stmt)
    cur = tdb.query(stmt, bindValues)

    stationByID = tdb.stationByID
    padSize = cmdenv.padSize
    planetary = cmdenv.planetary
    wantNoPlanet = cmdenv.noPlanet
    wantBlackMarket = cmdenv.blackMarket

    for (stationID, priceCr, demand) in cur:
        station = stationByID[stationID]
        if padSize and not station.checkPadSize(padSize):
            continue
        if planetary and not station.checkPlanetary(planetary):
            continue
        if wantNoPlanet and station.planetary != 'N':
            continue
        if wantBlackMarket and station.blackMarket != 'Y':
            continue
        if station in avoidStations:
            continue
        if station.system in avoidSystems:
            continue

        row = ResultRow()
        row.station = station
        if distanceFn:
            distance = distanceFn(row.station.system)
            if distance > maxLy:
                continue
            row.dist = distance
        row.price = priceCr
        row.demand = demand
        row.age = station.itemDataAgeStr
        results.rows.append(row)

    if not results.rows:
        raise NoDataError("No available items found")

    results.summary.sort = "Price"
    results.rows.sort(key=lambda result: result.demand, reverse=True)
    results.rows.sort(key=lambda result: result.price, reverse=True)
    if nearSystem and not cmdenv.sortByPrice:
        results.summary.sort = "Dist"
        results.rows.sort(key=lambda result: result.dist)

    limit = cmdenv.limit or 0
    if limit > 0:
        results.rows = results.rows[:limit]

    return results
Пример #8
0
def run(results, cmdenv, tdb):
    from commands.commandenv import ResultRow

    cmdenv = results.cmdenv
    tdb = cmdenv.tdb
    srcSystem = cmdenv.nearSystem

    results.summary = ResultRow()
    results.limit = cmdenv.limit

    fields = [
        "si.station_id",
        "JULIANDAY('NOW') - JULIANDAY(MAX(si.modified))",
        "stn.ls_from_star",
    ]

    joins = []
    wheres = []
    havings = []

    if cmdenv.minAge:
        wheres.append(
            "(JULIANDAY('NOW') - JULIANDAY(si.modified) >= {})".format(
                cmdenv.minAge))

    nearSys = cmdenv.nearSystem
    if nearSys:
        maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy
        maxLy2 = maxLy**2
        fields.append("dist2("
                      "sys.pos_x, sys.pos_y, sys.pos_z,"
                      "{}, {}, {}"
                      ") AS d2".format(
                          nearSys.posX,
                          nearSys.posY,
                          nearSys.posZ,
                      ))
        joins.append("INNER JOIN System sys USING (system_id)")
        wheres.append("""(
                sys.pos_x BETWEEN {} and {}
                AND sys.pos_y BETWEEN {} and {}
                AND sys.pos_z BETWEEN {} and {}
        )""".format(
            nearSys.posX - maxLy,
            nearSys.posX + maxLy,
            nearSys.posY - maxLy,
            nearSys.posY + maxLy,
            nearSys.posZ - maxLy,
            nearSys.posZ + maxLy,
        ))
        havings.append("d2 <= {}".format(maxLy2))
    else:
        fields.append("0")

    fieldStr = ','.join(fields)

    if joins:
        joinStr = ' '.join(joins)
    else:
        joinStr = ''

    if wheres:
        whereStr = 'WHERE ' + ' AND '.join(wheres)
    else:
        whereStr = ''

    if havings:
        haveStr = 'HAVING ' + ' AND '.join(havings)
    else:
        haveStr = ''

    stmt = """
            SELECT  {fields}
              FROM  StationItem as si
                    INNER JOIN Station stn USING (station_id)
                    {joins}
             {wheres}
             GROUP  BY 1
             {having}
             ORDER  BY 2 DESC
    """.format(
        fields=fieldStr,
        joins=joinStr,
        wheres=whereStr,
        having=haveStr,
    )

    cmdenv.DEBUG1(stmt)

    for (stnID, age, ls, dist2) in tdb.query(stmt):
        cmdenv.DEBUG2("{}:{}:{}", stnID, age, ls)
        row = ResultRow()
        row.station = tdb.stationByID[stnID]
        row.age = age
        if ls:
            row.ls = "{:n}".format(ls)
        else:
            row.ls = "?"
        row.dist = dist2**0.5
        results.rows.append(row)

    if cmdenv.route and len(results.rows) > 1:

        def walk(startNode, dist):
            rows = results.rows
            startNode = rows[startNode]
            openList = set(rows)
            path = [startNode]
            openList.remove(startNode)
            while len(path) < len(rows):
                lastNode = path[-1]
                distFn = lastNode.station.system.distanceTo
                nearest = min(openList,
                              key=lambda row: distFn(row.station.system))
                openList.remove(nearest)
                path.append(nearest)
                dist += distFn(nearest.station.system)
            return (path, dist)

        if cmdenv.near:
            bestPath = walk(0, results.rows[0].dist)
        else:
            bestPath = (results.rows, float("inf"))
            for i in range(len(results.rows)):
                path = walk(i, 0)
                if path[1] < bestPath[1]:
                    bestPath = path
        results.rows[:] = bestPath[0]

    if cmdenv.limit:
        results.rows[:] = results.rows[:cmdenv.limit]

    return results
Пример #9
0
def run(results, cmdenv, tdb):
    from commands.commandenv import ResultRow

    cmdenv = results.cmdenv
    tdb = cmdenv.tdb
    srcSystem = cmdenv.nearSystem

    results.summary = ResultRow()
    results.limit = cmdenv.limit

    fields = [
        "si.station_id",
        "JULIANDAY('NOW') - JULIANDAY(MAX(si.modified))",
        "stn.ls_from_star",
    ]

    joins = []
    wheres = []
    havings = []

    if cmdenv.minAge:
        wheres.append(
            "(JULIANDAY('NOW') - JULIANDAY(si.modified) >= {})"
            .format(cmdenv.minAge)
        )

    nearSys = cmdenv.nearSystem
    if nearSys:
        maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy
        maxLy2 = maxLy ** 2
        fields.append(
                "dist2("
                    "sys.pos_x, sys.pos_y, sys.pos_z,"
                    "{}, {}, {}"
                ") AS d2".format(
                    nearSys.posX,
                    nearSys.posY,
                    nearSys.posZ,
        ))
        joins.append("INNER JOIN System sys USING (system_id)")
        wheres.append("""(
                sys.pos_x BETWEEN {} and {}
                AND sys.pos_y BETWEEN {} and {}
                AND sys.pos_z BETWEEN {} and {}
        )""".format(
                nearSys.posX - maxLy,
                nearSys.posX + maxLy,
                nearSys.posY - maxLy,
                nearSys.posY + maxLy,
                nearSys.posZ - maxLy,
                nearSys.posZ + maxLy,
        ))
        havings.append("d2 <= {}".format(maxLy2))
    else:
        fields.append("0")

    fieldStr = ','.join(fields)

    if joins:
        joinStr = ' '.join(joins)
    else:
        joinStr = ''

    if wheres:
        whereStr = 'WHERE ' + ' AND '.join(wheres)
    else:
        whereStr = ''

    if havings:
        haveStr = 'HAVING ' + ' AND '.join(havings)
    else:
        haveStr = ''

    stmt = """
            SELECT  {fields}
              FROM  StationItem as si
                    INNER JOIN Station stn USING (station_id)
                    {joins}
             {wheres}
             GROUP  BY 1
             {having}
             ORDER  BY 2 DESC
    """.format(
            fields=fieldStr,
            joins=joinStr,
            wheres=whereStr,
            having=haveStr,
    )

    cmdenv.DEBUG1(stmt)

    for (stnID, age, ls, dist2) in tdb.query(stmt):
        cmdenv.DEBUG2("{}:{}:{}", stnID, age, ls)
        row = ResultRow()
        row.station = tdb.stationByID[stnID]
        row.age = age
        if ls:
            row.ls = "{:n}".format(ls)
        else:
            row.ls = "?"
        row.dist = dist2 ** 0.5
        results.rows.append(row)

    if cmdenv.route and len(results.rows) > 1:
        def walk(startNode, dist):
            rows = results.rows
            startNode = rows[startNode]
            openList = set(rows)
            path = [startNode]
            openList.remove(startNode)
            while len(path) < len(rows):
                lastNode = path[-1]
                distFn = lastNode.station.system.distanceTo
                nearest = min(openList, key=lambda row: distFn(row.station.system))
                openList.remove(nearest)
                path.append(nearest)
                dist += distFn(nearest.station.system)
            return path, dist
        if cmdenv.near:
            bestPath = walk(0, results.rows[0].dist)
        else:
            bestPath = (results.rows, float("inf"))
            for i in range(len(results.rows)):
                path = walk(i, 0)
                if path[1] < bestPath[1]:
                    bestPath = path
        results.rows[:] = bestPath[0]

    if cmdenv.limit:
        results.rows[:] = results.rows[:cmdenv.limit]

    return results
Пример #10
0
def run(results, cmdenv, tdb):
    cmdenv = results.cmdenv
    tdb = cmdenv.tdb
    srcSystem = cmdenv.nearSystem

    ly = cmdenv.maxLyPer
    if ly is None:
        ly = tdb.maxSystemLinkLy

    results.summary = ResultRow()
    results.summary.near = srcSystem
    results.summary.ly = ly
    results.summary.stations = 0

    distances = {srcSystem: 0.0}

    # Calculate the bounding dimensions
    for destSys, dist in tdb.genSystemsInRange(srcSystem, ly):
        distances[destSys] = dist

    showStations = cmdenv.detail
    wantStations = cmdenv.stations
    padSize = cmdenv.padSize
    planetary = cmdenv.planetary
    wantNoPlanet = cmdenv.noPlanet
    wantTrading = cmdenv.trading
    wantShipYard = cmdenv.shipyard
    wantBlackMarket = cmdenv.blackMarket
    wantOutfitting = cmdenv.outfitting
    wantRearm = cmdenv.rearm
    wantRefuel = cmdenv.refuel
    wantRepair = cmdenv.repair

    def station_filter(stations):
        for station in stations:
            if wantNoPlanet and station.planetary != 'N':
                continue
            if wantTrading and not station.isTrading:
                continue
            if wantBlackMarket and station.blackMarket != 'Y':
                continue
            if wantShipYard and station.shipyard != 'Y':
                continue
            if padSize and not station.checkPadSize(padSize):
                continue
            if planetary and not station.checkPlanetary(planetary):
                continue
            if wantOutfitting and station.outfitting != 'Y':
                continue
            if wantRearm and station.rearm != 'Y':
                continue
            if wantRefuel and station.refuel != 'Y':
                continue
            if wantRepair and station.repair != 'Y':
                continue
            yield station

    for (system, dist) in sorted(distances.items(), key=lambda x: x[1]):
        if showStations or wantStations:
            stations = []
            for (station) in station_filter(system.stations):
                stations.append(
                    ResultRow(
                        station=station,
                        age=station.itemDataAgeStr,
                    ))
            if not stations and wantStations:
                continue

        row = ResultRow()
        row.system = system
        row.dist = dist
        row.stations = stations if showStations else []
        results.rows.append(row)
        results.summary.stations += len(row.stations)

    return results
Пример #11
0
def run(results, cmdenv, tdb):
    from commands.commandenv import ResultRow

    srcSystem, dstSystem = cmdenv.origPlace, cmdenv.destPlace
    if isinstance(srcSystem, Station):
        srcSystem = srcSystem.system
    if isinstance(dstSystem, Station):
        dstSystem = dstSystem.system

    maxLyPer = cmdenv.maxLyPer or tdb.maxSystemLinkLy

    cmdenv.DEBUG0("Route from {} to {} with max {}ly per jump.",
                    srcSystem.name(), dstSystem.name(), maxLyPer)

    # Build a list of src->dst pairs
    hops = [ [ srcSystem, None ] ]
    if cmdenv.viaPlaces:
        for hop in cmdenv.viaPlaces:
            hops[-1][1] = hop
            hops.append([hop, None])
    hops[-1][1] = dstSystem

    avoiding = [
        avoid for avoid in cmdenv.avoidPlaces
        if isinstance(avoid, System)
    ]

    route = [ ]
    stationInterval = cmdenv.stationInterval
    for hop in hops:
        hopRoute = tdb.getRoute(
                hop[0], hop[1],
                maxLyPer,
                avoiding,
                stationInterval=stationInterval,
                )
        if not hopRoute:
            raise NoRouteError(
                    "No route found between {} and {} "
                    "with a max {}ly/jump limit.".format(
                        hop[0].name(), hop[1].name(),
                        maxLyPer,
            ))
        route = route[:-1] + hopRoute

    results.summary = ResultRow(
                fromSys=srcSystem,
                toSys=dstSystem,
                maxLy=maxLyPer,
            )

    lastSys, totalLy, dirLy = srcSystem, 0.00, 0.00
    maxPadSize = cmdenv.padSize

    for (jumpSys, dist) in route:
        jumpLy = lastSys.distanceTo(jumpSys)
        totalLy += jumpLy
        if cmdenv.detail:
            dirLy = jumpSys.distanceTo(dstSystem)
        row = ResultRow(
            action='Via',
            system=jumpSys,
            jumpLy=jumpLy,
            totalLy=totalLy,
            dirLy=dirLy,
            )
        row.stations = []
        if cmdenv.stations:
            for (station) in jumpSys.stations:
                if maxPadSize and not station.checkPadSize(maxPadSize):
                    continue
                rr = ResultRow(
                    station=station,
                    age=station.itemDataAgeStr,
                )
                row.stations.append(rr)
        results.rows.append(row)
        lastSys = jumpSys
    results.rows[0].action='Depart'
    results.rows[-1].action='Arrive'

    return results
Пример #12
0
def run(results, cmdenv, tdb):
    from commands.commandenv import ResultRow

    srcSystem, dstSystem = cmdenv.origPlace, cmdenv.destPlace
    if isinstance(srcSystem, Station):
        srcSystem = srcSystem.system
    if isinstance(dstSystem, Station):
        dstSystem = dstSystem.system

    maxLyPer = cmdenv.maxLyPer or tdb.maxSystemLinkLy

    cmdenv.DEBUG0("Route from {} to {} with max {}ly per jump.",
                  srcSystem.name(), dstSystem.name(), maxLyPer)

    # Build a list of src->dst pairs
    hops = [[srcSystem, None]]
    if cmdenv.viaPlaces:
        for hop in cmdenv.viaPlaces:
            hops[-1][1] = hop
            hops.append([hop, None])
    hops[-1][1] = dstSystem

    avoiding = [
        avoid for avoid in cmdenv.avoidPlaces if isinstance(avoid, System)
    ]

    route = []
    stationInterval = cmdenv.stationInterval
    for hop in hops:
        hopRoute = tdb.getRoute(
            hop[0],
            hop[1],
            maxLyPer,
            avoiding,
            stationInterval=stationInterval,
        )
        if not hopRoute:
            raise NoRouteError("No route found between {} and {} "
                               "with a max {}ly/jump limit.".format(
                                   hop[0].name(),
                                   hop[1].name(),
                                   maxLyPer,
                               ))
        route = route[:-1] + hopRoute

    results.summary = ResultRow(
        fromSys=srcSystem,
        toSys=dstSystem,
        maxLy=maxLyPer,
    )

    lastSys, totalLy, dirLy = srcSystem, 0.00, 0.00
    maxPadSize = cmdenv.padSize
    planetary = cmdenv.planetary
    noPlanet = cmdenv.noPlanet

    for (jumpSys, dist) in route:
        jumpLy = lastSys.distanceTo(jumpSys)
        totalLy += jumpLy
        if cmdenv.detail:
            dirLy = jumpSys.distanceTo(dstSystem)
        row = ResultRow(
            action='Via',
            system=jumpSys,
            jumpLy=jumpLy,
            totalLy=totalLy,
            dirLy=dirLy,
        )
        row.stations = []
        if cmdenv.stations:
            for (station) in jumpSys.stations:
                if maxPadSize and not station.checkPadSize(maxPadSize):
                    continue
                if planetary and not station.checkPlanetary(planetary):
                    continue
                if noPlanet and station.planetary != 'N':
                    continue
                rr = ResultRow(
                    station=station,
                    age=station.itemDataAgeStr,
                )
                row.stations.append(rr)
        results.rows.append(row)
        lastSys = jumpSys
    results.rows[0].action = 'Depart'
    results.rows[-1].action = 'Arrive'

    return results
Пример #13
0
def run(results, cmdenv, tdb):
    from commands.commandenv import ResultRow

    if cmdenv.lt and cmdenv.gt:
        if cmdenv.lt <= cmdenv.gt:
            raise CommandLineError("--gt must be lower than --lt")

    item = tdb.lookupItem(cmdenv.item)
    cmdenv.DEBUG0("Looking up item {} (#{})", item.name(), item.ID)

    avoidSystems = {s for s in cmdenv.avoidPlaces if isinstance(s, System)}
    avoidStations = {s for s in cmdenv.avoidPlaces if isinstance(s, Station)}

    results.summary = ResultRow()
    results.summary.item = item
    results.summary.avoidSystems = avoidSystems
    results.summary.avoidStations = avoidStations

    if cmdenv.detail:
        avgPrice = tdb.query("""
            SELECT AVG(si.demand_price)
              FROM StationItem AS si
             WHERE si.item_id = ? AND si.demand_price > 0
        """, [item.ID]).fetchone()[0]
        results.summary.avg = int(avgPrice)

    # Constraints
    tables = "StationItem AS si"
    constraints = [
        "(item_id = {} AND demand_price > 0)".format(item.ID),
    ]
    columns = [
        'si.station_id',
        'si.demand_price',
        'si.demand_units',
    ]
    bindValues = []

    if cmdenv.demand:
        constraints.append("(demand_units >= ?)")
        bindValues.append(cmdenv.demand)

    if cmdenv.lt:
        constraints.append("(demand_price < ?)")
        bindValues.append(cmdenv.lt)
    if cmdenv.gt:
        constraints.append("(demand_price > ?)")
        bindValues.append(cmdenv.gt)

    nearSystem = cmdenv.nearSystem
    if nearSystem:
        maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy
        results.summary.near = nearSystem
        results.summary.ly = maxLy
        distanceFn = nearSystem.distanceTo
    else:
        distanceFn = None

    whereClause = ' AND '.join(constraints)
    stmt = """SELECT DISTINCT {columns} FROM {tables} WHERE {where}""".format(
        columns=','.join(columns),
        tables=tables,
        where=whereClause
    )
    cmdenv.DEBUG0('SQL: {}', stmt)
    cur = tdb.query(stmt, bindValues)

    stationByID = tdb.stationByID
    padSize = cmdenv.padSize
    wantBlackMarket = cmdenv.blackMarket

    for (stationID, priceCr, demand) in cur:
        station = stationByID[stationID]
        if padSize and not station.checkPadSize(padSize):
            continue
        if wantBlackMarket and station.blackMarket != 'Y':
            continue
        if station in avoidStations:
            continue
        if station.system in avoidSystems:
            continue

        row = ResultRow()
        row.station = station
        if distanceFn:
            distance = distanceFn(row.station.system)
            if distance > maxLy:
                continue
            row.dist = distance
        row.price = priceCr
        row.demand = demand
        row.age = station.itemDataAgeStr
        results.rows.append(row)

    if not results.rows:
        raise NoDataError("No available items found")

    results.summary.sort = "Price"
    results.rows.sort(key=lambda result: result.demand, reverse=True)
    results.rows.sort(key=lambda result: result.price, reverse=True)
    if nearSystem and not cmdenv.sortByPrice:
        results.summary.sort = "Dist"
        results.rows.sort(key=lambda result: result.dist)

    limit = cmdenv.limit or 0
    if limit > 0:
        results.rows = results.rows[:limit]

    return results
Пример #14
0
def run(results, cmdenv, tdb):
    if cmdenv.lt and cmdenv.gt:
        if cmdenv.lt <= cmdenv.gt:
            raise CommandLineError("--gt must be lower than --lt")

    # Find out what we're looking for.
    queries, mode = get_lookup_list(cmdenv, tdb)
    cmdenv.DEBUG0("{} query: {}", mode, queries.values())

    avoidSystems = {s for s in cmdenv.avoidPlaces if isinstance(s, System)}
    avoidStations = {s for s in cmdenv.avoidPlaces if isinstance(s, Station)}

    # Summarize
    results.summary = ResultRow()
    results.summary.mode = mode
    results.summary.queries = queries
    results.summary.oneStop = cmdenv.oneStop
    results.summary.avoidSystems = avoidSystems
    results.summary.avoidStations = avoidStations

    # In single mode with detail enabled, add average reports.
    # Thus if you're looking up "algae" or the "asp", it'll
    # tell you the average/ship cost.
    singleMode = len(queries) == 1
    if singleMode and cmdenv.detail:
        first = list(queries.values())[0]
        if mode is SHIP_MODE:
            results.summary.avg = first.cost
        else:
            avgPrice = tdb.query("""
                SELECT AVG(si.supply_price)
                  FROM StationItem AS si
                 WHERE si.item_id = ? AND si.supply_price > 0
            """, [first.ID]).fetchone()[0]
            results.summary.avg = int(avgPrice)

    # System-based search
    nearSystem = cmdenv.nearSystem
    if nearSystem:
        maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy
        results.summary.near = nearSystem
        results.summary.ly = maxLy
        distanceFn = nearSystem.distanceTo
    else:
        distanceFn = None

    oneStopMode = cmdenv.oneStop
    padSize = cmdenv.padSize
    wantBlackMarket = cmdenv.blackMarket

    stations = defaultdict(list)
    stationByID = tdb.stationByID

    cur = sql_query(cmdenv, tdb, queries, mode)
    for (ID, stationID, price, units) in cur:
        station = stationByID[stationID]
        if padSize and not station.checkPadSize(padSize):
            continue
        if wantBlackMarket and station.blackMarket != 'Y':
            continue
        if station in avoidStations:
            continue
        if station.system in avoidSystems:
            continue

        row = ResultRow()
        row.station = station
        if distanceFn:
            distance = distanceFn(row.station.system)
            if distance > maxLy:
                continue
            row.dist = distance
        row.item = queries[ID]
        row.price = price
        row.units = units
        row.age = station.itemDataAgeStr
        if oneStopMode:
            stationRows = stations[stationID]
            stationRows.append(row)
            if len(stationRows) >= len(queries):
                results.rows.extend(stationRows)
        else:
            results.rows.append(row)

    if not results.rows:
        if oneStopMode and len(stations):
            raise NoDataError("No one-stop stations found")
        raise NoDataError("No available items found")

    if oneStopMode and not singleMode:
        results.rows.sort(key=lambda result: result.item.name())
    results.rows.sort(key=lambda result: result.station.name())
    if cmdenv.sortByUnits:
        results.summary.sort = "units"
        results.rows.sort(key=lambda result: result.price)
        results.rows.sort(key=lambda result: result.units, reverse=True)
    else:
        if not oneStopMode:
            results.summary.sort = "Price"
            results.rows.sort(key=lambda result: result.units, reverse=True)
            results.rows.sort(key=lambda result: result.price)
        if nearSystem and not cmdenv.sortByPrice:
            results.summary.sort = "Ly"
            results.rows.sort(key=lambda result: result.dist)

    limit = cmdenv.limit or 0
    if limit > 0:
        results.rows = results.rows[:limit]

    return results
Пример #15
0
def run(results, cmdenv, tdb):
    cmdenv.DEBUG1("loading trades")

    if tdb.tradingCount == 0:
        raise NoDataError("Database does not contain any profitable trades.")

    # Instantiate the calculator object
    calc = TradeCalc(tdb, cmdenv)

    validateRunArguments(tdb, cmdenv, calc)

    origPlace, viaSet = cmdenv.origPlace, cmdenv.viaSet
    avoidPlaces = cmdenv.avoidPlaces
    stopStations = cmdenv.destinations
    goalSystem = cmdenv.goalSystem
    maxLs = cmdenv.maxLs

    # seed the route table with starting places
    startCr = cmdenv.credits - cmdenv.insurance
    routes = [
        Route(
            stations=(src,),
            hops=(),
            jumps=(),
            startCr=startCr,
            gainCr=0,
            score=0
        )
        for src in cmdenv.origins
    ]

    numHops = cmdenv.hops
    lastHop = numHops - 1
    viaStartPos = 1 if origPlace else 0

    cmdenv.DEBUG1("numHops {}, vias {}, adhocHops {}",
                numHops, len(viaSet), cmdenv.adhocHops)

    results.summary = ResultRow()
    results.summary.exception = ""

    if cmdenv.loop:
        routePickPred = lambda route: \
            route.lastStation is route.firstStation
    elif cmdenv.shorten:
        if not cmdenv.destPlace:
            routePickPred = lambda route: \
                route.lastStation is route.firstStation
        elif isinstance(cmdenv.destPlace, System):
            routePickPred = lambda route: \
                route.lastSystem is cmdenv.destPlace
        else:
            routePickPred = lambda route: \
                route.lastStation is cmdenv.destPlace
    else:
        routePickPred = None

    pickedRoutes = []

    pruneMod = cmdenv.pruneScores / 100

    if cmdenv.loop:
        distancePruning = lambda rt, distLeft: \
            rt.lastSystem.distanceTo(rt.firstSystem) <= distLeft
    elif cmdenv.destPlace and not cmdenv.direct:
        distancePruning = lambda rt, distLeft: \
            any(
                stop for stop in stopSystems
                if rt.lastSystem.distanceTo(stop) <= distLeft
            )
    else:
        distancePruning = False

    if distancePruning:
        maxHopDistLy = cmdenv.maxJumpsPer * cmdenv.maxLyPer
        if not cmdenv.loop:
            stopSystems = {stop.system for stop in stopStations}

    for hopNo in range(numHops):
        restrictTo = None
        if hopNo == lastHop and stopStations:
            restrictTo = set(stopStations)
            manualRestriction = bool(cmdenv.destPlace)
        elif len(viaSet) > cmdenv.adhocHops:
            restrictTo = viaSet
            manualRestriction = True

        if distancePruning:
            preCrop = len(routes)
            distLeft = maxHopDistLy * (numHops - hopNo)
            routes = [rt for rt in routes if distancePruning(rt, distLeft)]
            if not routes:
                if pickedRoutes:
                    break
                raise NoDataError(
                    "No routes are in-range of any end stations at the end of hop {}"
                    .format(hopNo)
                )
            pruned = preCrop - len(routes)
            if pruned:
                cmdenv.NOTE("Pruned {} origins too far from any end stations", pruned)

        if hopNo >= 1 and (cmdenv.maxRoutes or pruneMod):
            routes.sort()
            if pruneMod and hopNo + 1 >= cmdenv.pruneHops and len(routes) > 10:
                crop = int(len(routes) * pruneMod)
                routes = routes[:-crop]
                cmdenv.NOTE("Pruned {} origins", crop)

            if cmdenv.maxRoutes and len(routes) > cmdenv.maxRoutes:
                routes = routes[:cmdenv.maxRoutes]

        if cmdenv.progress:
            extra = ""
            if hopNo > 0 and cmdenv.detail > 1:
                extra = extraRouteProgress(routes)
            print(
                "* Hop {:3n}: {:.>10n} origins {}"
                .format(hopNo+1, len(routes), extra)
            )
        elif cmdenv.debug:
            cmdenv.DEBUG0("Hop {}...", hopNo+1)

        try:
            newRoutes = calc.getBestHops(routes, restrictTo=restrictTo)
        except NoHopsError:
            if hopNo == 0 and len(cmdenv.origSystems) == 1:
                raise NoDataError(
                    "Couldn't find any trading links within {} x {}ly jumps of {}."
                    .format(
                        cmdenv.maxJumpsPer,
                        cmdenv.maxLyPer,
                        cmdenv.origSystems[0].name(),
                    )
                )
            raise NoDataError(
                "No routes had reachable trading links at hop #{}".format(hopNo + 1)
            )

        if not newRoutes:
            if pickedRoutes:
                break
            checkReachability(tdb, cmdenv)
            if hopNo > 0:
                if restrictTo and manualRestriction:
                    results.summary.exception += routeFailedRestrictions(
                        tdb, cmdenv, restrictTo, maxLs, hopNo
                    )
                    break
                results.summary.exception += (
                    "SORRY: Could not find profitable destinations "
                    "beyond hop #{:n}\n"
                    .format(hopNo + 1)
                )
                break
            if hopNo == 0:
                if cmdenv.origPlace and len(routes) == 1:
                    errText = (
                        "No profitable buyers found for the goods at {}.\n"
                        "\n"
                        "You may want to try:\n"
                        "  {} local \"{}\" --ly {} -vv --stations --trading"
                        .format(
                            routes[0].lastStation.name(),
                            sys.argv[0], cmdenv.origPlace.system.name(),
                            cmdenv.maxJumpsPer * cmdenv.maxLyPer,
                        )
                    )
                    if isinstance(cmdenv.origPlace, Station):
                        errText += (
                            "\n"
                            "or:\n"
                            "  {} market \"{}\" --sell -vv"
                            .format(
                                sys.argv[0], cmdenv.origPlace.name(),
                            )
                        )
                    raise NoDataError(errText)

        routes = newRoutes
        if routes and goalSystem:
            # Promote the winning route to the top of the list
            # while leaving the remainder of the list intact
            routes.sort(
                key=lambda route:
                    0 if route.lastSystem is goalSystem else 1
            )
            if routes[0].lastSystem is goalSystem:
                cmdenv.NOTE("Goal system reached!")
                routes = routes[:1]
                break

        if routePickPred:
            pickedRoutes.extend(
                route for route in routes if routePickPred(route)
            )

    if cmdenv.loop or cmdenv.shorten:
        cmdenv.DEBUG0("Using {} picked routes", len(pickedRoutes))
        routes = pickedRoutes
        # normalise the scores for fairness...
        for route in routes:
            cmdenv.DEBUG0(
                "{} hops, {} score, {} gpt",
                len(route.hops), route.score, route.gpt
            )
            route.score /= len(route.hops)

    if not routes:
        raise NoDataError(
            "No profitable trades matched your critera, "
            "or price data along the route is missing."
        )

    if viaSet:
        routes, caution = filterByVia(routes, viaSet, viaStartPos)
        if caution:
            results.summary.exception += caution + "\n"

    routes.sort()
    results.data = routes

    return results
Пример #16
0
def run(results, cmdenv, tdb):
    if cmdenv.lt and cmdenv.gt:
        if cmdenv.lt <= cmdenv.gt:
            raise CommandLineError("--gt must be lower than --lt")

    # Find out what we're looking for.
    queries, mode = get_lookup_list(cmdenv, tdb)
    cmdenv.DEBUG0("{} query: {}", mode, queries.values())

    avoidSystems = {s for s in cmdenv.avoidPlaces if isinstance(s, System)}
    avoidStations = {s for s in cmdenv.avoidPlaces if isinstance(s, Station)}

    # Summarize
    results.summary = ResultRow()
    results.summary.mode = mode
    results.summary.queries = queries
    results.summary.oneStop = cmdenv.oneStop
    results.summary.avoidSystems = avoidSystems
    results.summary.avoidStations = avoidStations

    # In single mode with detail enabled, add average reports.
    # Thus if you're looking up "algae" or the "asp", it'll
    # tell you the average/ship cost.
    singleMode = len(queries) == 1
    if singleMode and cmdenv.detail:
        first = list(queries.values())[0]
        if mode is SHIP_MODE:
            results.summary.avg = first.cost
        else:
            avgPrice = tdb.query(
                """
                SELECT AVG(si.supply_price)
                  FROM StationItem AS si
                 WHERE si.item_id = ? AND si.supply_price > 0
            """, [first.ID]).fetchone()[0]
            if not avgPrice:
                avgPrice = 0
            results.summary.avg = int(avgPrice)

    # System-based search
    nearSystem = cmdenv.nearSystem
    if nearSystem:
        maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy
        results.summary.near = nearSystem
        results.summary.ly = maxLy
        distanceFn = nearSystem.distanceTo
    else:
        distanceFn = None

    oneStopMode = cmdenv.oneStop
    padSize = cmdenv.padSize
    planetary = cmdenv.planetary
    wantNoPlanet = cmdenv.noPlanet
    wantBlackMarket = cmdenv.blackMarket

    stations = defaultdict(list)
    stationByID = tdb.stationByID

    cur = sql_query(cmdenv, tdb, queries, mode)
    for (ID, stationID, price, units) in cur:
        station = stationByID[stationID]
        if padSize and not station.checkPadSize(padSize):
            continue
        if planetary and not station.checkPlanetary(planetary):
            continue
        if wantNoPlanet and station.planetary != 'N':
            continue
        if wantBlackMarket and station.blackMarket != 'Y':
            continue
        if station in avoidStations:
            continue
        if station.system in avoidSystems:
            continue

        row = ResultRow()
        row.station = station
        if distanceFn:
            distance = distanceFn(row.station.system)
            if distance > maxLy:
                continue
            row.dist = distance
        row.item = queries[ID]
        row.price = price
        row.units = units
        row.age = station.itemDataAgeStr
        if oneStopMode:
            stationRows = stations[stationID]
            stationRows.append(row)
            if len(stationRows) >= len(queries):
                results.rows.extend(stationRows)
        else:
            results.rows.append(row)

    if not results.rows:
        if oneStopMode and len(stations):
            raise NoDataError("No one-stop stations found")
        raise NoDataError("No available items found")

    if oneStopMode and not singleMode:
        results.rows.sort(key=lambda result: result.item.name())
    results.rows.sort(key=lambda result: result.station.name())
    if cmdenv.sortByUnits:
        results.summary.sort = "units"
        results.rows.sort(key=lambda result: result.price)
        results.rows.sort(key=lambda result: result.units, reverse=True)
    else:
        if not oneStopMode:
            results.summary.sort = "Price"
            results.rows.sort(key=lambda result: result.units, reverse=True)
            results.rows.sort(key=lambda result: result.price)
        if nearSystem and not cmdenv.sortByPrice:
            results.summary.sort = "Ly"
            results.rows.sort(key=lambda result: result.dist)

    limit = cmdenv.limit or 0
    if limit > 0:
        results.rows = results.rows[:limit]

    return results
Пример #17
0
def run(results, cmdenv, tdb):
    cmdenv = results.cmdenv
    tdb = cmdenv.tdb
    srcSystem = cmdenv.nearSystem

    ly = cmdenv.maxLyPer
    if ly is None:
        ly = tdb.maxSystemLinkLy

    results.summary = ResultRow()
    results.summary.near = srcSystem
    results.summary.ly = ly
    results.summary.stations = 0

    distances = { srcSystem: 0.0 }

    # Calculate the bounding dimensions
    for destSys, dist in tdb.genSystemsInRange(srcSystem, ly):
        distances[destSys] = dist

    showStations = cmdenv.detail
    wantStations = cmdenv.stations
    padSize = cmdenv.padSize
    wantTrading = cmdenv.trading
    wantShipYard = cmdenv.shipyard
    wantBlackMarket = cmdenv.blackMarket
    wantOutfitting = cmdenv.outfitting
    wantRearm = cmdenv.rearm
    wantRefuel = cmdenv.refuel
    wantRepair = cmdenv.repair

    def station_filter(stations):
        for station in stations:
            if wantTrading and not station.isTrading:
                continue
            if wantBlackMarket and station.blackMarket != 'Y':
                continue
            if wantShipYard and station.shipyard != 'Y':
                continue
            if padSize and not station.checkPadSize(padSize):
                continue
            if wantOutfitting and station.outfitting != 'Y':
                continue
            if wantRearm and station.rearm != 'Y':
                continue
            if wantRefuel and station.refuel != 'Y':
                continue
            if wantRepair and station.repair != 'Y':
                continue
            yield station

    for (system, dist) in sorted(distances.items(), key=lambda x: x[1]):
        if showStations or wantStations:
            stations = []
            for (station) in station_filter(system.stations):
                stations.append(
                    ResultRow(
                        station=station,
                        age=station.itemDataAgeStr,
                    )
                )
            if not stations and wantStations:
                continue

        row = ResultRow()
        row.system = system
        row.dist = dist
        row.stations = stations if showStations else []
        results.rows.append(row)
        results.summary.stations += len(row.stations)

    return results